ConversationActivity.java

   1package eu.siacs.conversations.ui;
   2
   3import java.io.FileNotFoundException;
   4import java.lang.ref.WeakReference;
   5import java.util.ArrayList;
   6import java.util.Hashtable;
   7import java.util.List;
   8
   9import eu.siacs.conversations.R;
  10import eu.siacs.conversations.entities.Account;
  11import eu.siacs.conversations.entities.Contact;
  12import eu.siacs.conversations.entities.Conversation;
  13import eu.siacs.conversations.entities.Message;
  14import eu.siacs.conversations.services.ImageProvider;
  15import eu.siacs.conversations.utils.ExceptionHelper;
  16import eu.siacs.conversations.utils.UIHelper;
  17import android.net.Uri;
  18import android.os.AsyncTask;
  19import android.os.Bundle;
  20import android.preference.PreferenceManager;
  21import android.provider.MediaStore;
  22import android.app.AlertDialog;
  23import android.app.FragmentTransaction;
  24import android.app.PendingIntent;
  25import android.content.Context;
  26import android.content.DialogInterface;
  27import android.content.DialogInterface.OnClickListener;
  28import android.content.IntentSender.SendIntentException;
  29import android.content.Intent;
  30import android.content.SharedPreferences;
  31import android.content.res.Resources;
  32import android.graphics.Bitmap;
  33import android.graphics.Color;
  34import android.graphics.Typeface;
  35import android.graphics.drawable.BitmapDrawable;
  36import android.graphics.drawable.Drawable;
  37import android.support.v4.widget.SlidingPaneLayout;
  38import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
  39import android.util.DisplayMetrics;
  40import android.util.Log;
  41import android.view.KeyEvent;
  42import android.view.LayoutInflater;
  43import android.view.Menu;
  44import android.view.MenuItem;
  45import android.view.View;
  46import android.view.ViewGroup;
  47import android.widget.AdapterView;
  48import android.widget.AdapterView.OnItemClickListener;
  49import android.widget.ArrayAdapter;
  50import android.widget.CheckBox;
  51import android.widget.ListView;
  52import android.widget.PopupMenu;
  53import android.widget.PopupMenu.OnMenuItemClickListener;
  54import android.widget.TextView;
  55import android.widget.ImageView;
  56import android.widget.Toast;
  57
  58public class ConversationActivity extends XmppActivity {
  59
  60	public static final String VIEW_CONVERSATION = "viewConversation";
  61	public static final String CONVERSATION = "conversationUuid";
  62	public static final String TEXT = "text";
  63	public static final String PRESENCE = "eu.siacs.conversations.presence";
  64
  65	public static final int REQUEST_SEND_MESSAGE = 0x75441;
  66	public static final int REQUEST_DECRYPT_PGP = 0x76783;
  67	private static final int REQUEST_ATTACH_FILE_DIALOG = 0x48502;
  68	private static final int REQUEST_IMAGE_CAPTURE = 0x33788;
  69	private static final int REQUEST_RECORD_AUDIO = 0x46189;
  70	private static final int REQUEST_SEND_PGP_IMAGE = 0x53883;
  71	public static final int REQUEST_ENCRYPT_MESSAGE = 0x378018;
  72	
  73	private static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x92734;
  74	private static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x84123;
  75	private static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x75291;
  76
  77	protected SlidingPaneLayout spl;
  78
  79	private List<Conversation> conversationList = new ArrayList<Conversation>();
  80	private Conversation selectedConversation = null;
  81	private ListView listView;
  82	
  83	private boolean paneShouldBeOpen = true;
  84	private boolean useSubject = true;
  85	private ArrayAdapter<Conversation> listAdapter;
  86	
  87	public Message pendingMessage = null;
  88
  89	private OnConversationListChangedListener onConvChanged = new OnConversationListChangedListener() {
  90
  91		@Override
  92		public void onConversationListChanged() {
  93			runOnUiThread(new Runnable() {
  94
  95				@Override
  96				public void run() {
  97					updateConversationList();
  98					if (paneShouldBeOpen) {
  99						if (conversationList.size() >= 1) {
 100							swapConversationFragment();
 101						} else {
 102							startActivity(new Intent(getApplicationContext(),
 103									ContactsActivity.class));
 104							finish();
 105						}
 106					}
 107					ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
 108							.findFragmentByTag("conversation");
 109					if (selectedFragment != null) {
 110						selectedFragment.updateMessages();
 111					}
 112				}
 113			});
 114		}
 115	};
 116	
 117	protected ConversationActivity activity = this;
 118	private DisplayMetrics metrics;
 119	private Toast prepareImageToast;
 120
 121	public List<Conversation> getConversationList() {
 122		return this.conversationList;
 123	}
 124
 125	public Conversation getSelectedConversation() {
 126		return this.selectedConversation;
 127	}
 128	
 129	public void setSelectedConversation(Conversation conversation) {
 130		this.selectedConversation = conversation;
 131	}
 132
 133	public ListView getConversationListView() {
 134		return this.listView;
 135	}
 136
 137	public SlidingPaneLayout getSlidingPaneLayout() {
 138		return this.spl;
 139	}
 140
 141	public boolean shouldPaneBeOpen() {
 142		return paneShouldBeOpen;
 143	}
 144
 145	@Override
 146	protected void onCreate(Bundle savedInstanceState) {
 147
 148		metrics = getResources().getDisplayMetrics();
 149		
 150		super.onCreate(savedInstanceState);
 151
 152		setContentView(R.layout.fragment_conversations_overview);
 153
 154		listView = (ListView) findViewById(R.id.list);
 155
 156		this.listAdapter = new ArrayAdapter<Conversation>(this,
 157				R.layout.conversation_list_row, conversationList) {
 158			@Override
 159			public View getView(int position, View view, ViewGroup parent) {
 160				if (view == null) {
 161					LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 162					view = (View) inflater.inflate(
 163							R.layout.conversation_list_row, null);
 164				}
 165				Conversation conv;
 166				if (conversationList.size() > position) {
 167					conv = getItem(position);
 168				} else {
 169					return view;
 170				}
 171				if (!spl.isSlideable()) {
 172					if (conv == getSelectedConversation()) {
 173						view.setBackgroundColor(0xffdddddd);
 174					} else {
 175						view.setBackgroundColor(Color.TRANSPARENT);
 176					}
 177				} else {
 178					view.setBackgroundColor(Color.TRANSPARENT);
 179				}
 180				TextView convName = (TextView) view
 181						.findViewById(R.id.conversation_name);
 182				convName.setText(conv.getName(useSubject));
 183				TextView convLastMsg = (TextView) view
 184						.findViewById(R.id.conversation_lastmsg);
 185				ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage);
 186				
 187				Message latestMessage = conv.getLatestMessage();
 188				
 189				if (latestMessage.getType() == Message.TYPE_TEXT) {
 190					if ((latestMessage.getEncryption() != Message.ENCRYPTION_PGP)&&(latestMessage.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED)) {
 191						convLastMsg.setText(conv.getLatestMessage().getBody());
 192					} else {
 193						convLastMsg.setText(getText(R.string.encrypted_message_received));
 194					}
 195					convLastMsg.setVisibility(View.VISIBLE);
 196					imagePreview.setVisibility(View.GONE);
 197				} else if (latestMessage.getType() == Message.TYPE_IMAGE) {
 198					if (latestMessage.getStatus() >= Message.STATUS_RECIEVED) {
 199						convLastMsg.setVisibility(View.GONE);
 200						imagePreview.setVisibility(View.VISIBLE);
 201						loadBitmap(latestMessage, imagePreview);
 202					} else {
 203						convLastMsg.setVisibility(View.VISIBLE);
 204						imagePreview.setVisibility(View.GONE);
 205						if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) {
 206							convLastMsg.setText(getText(R.string.image_offered_for_download));
 207						} else if (latestMessage.getStatus() == Message.STATUS_RECIEVING) {
 208							convLastMsg.setText(getText(R.string.receiving_image));
 209						} else {
 210							convLastMsg.setText("");
 211						}
 212					}
 213				}
 214				
 215				
 216
 217				if (!conv.isRead()) {
 218					convName.setTypeface(null, Typeface.BOLD);
 219					convLastMsg.setTypeface(null, Typeface.BOLD);
 220				} else {
 221					convName.setTypeface(null, Typeface.NORMAL);
 222					convLastMsg.setTypeface(null, Typeface.NORMAL);
 223				}
 224
 225				((TextView) view.findViewById(R.id.conversation_lastupdate))
 226						.setText(UIHelper.readableTimeDifference(getContext(), conv
 227								.getLatestMessage().getTimeSent()));
 228
 229				ImageView profilePicture = (ImageView) view
 230						.findViewById(R.id.conversation_image);
 231				profilePicture.setImageBitmap(UIHelper.getContactPicture(
 232						conv, 56, activity.getApplicationContext(), false));
 233				
 234				return view;
 235			}
 236
 237		};
 238
 239		listView.setAdapter(this.listAdapter);
 240
 241		listView.setOnItemClickListener(new OnItemClickListener() {
 242
 243			@Override
 244			public void onItemClick(AdapterView<?> arg0, View clickedView,
 245					int position, long arg3) {
 246				paneShouldBeOpen = false;
 247				if (getSelectedConversation() != conversationList.get(position)) {
 248					setSelectedConversation(conversationList.get(position));
 249					swapConversationFragment(); // .onBackendConnected(conversationList.get(position));
 250				} else {
 251					spl.closePane();
 252				}
 253			}
 254		});
 255		spl = (SlidingPaneLayout) findViewById(R.id.slidingpanelayout);
 256		spl.setParallaxDistance(150);
 257		spl.setShadowResource(R.drawable.es_slidingpane_shadow);
 258		spl.setSliderFadeColor(0);
 259		spl.setPanelSlideListener(new PanelSlideListener() {
 260
 261			@Override
 262			public void onPanelOpened(View arg0) {
 263				paneShouldBeOpen = true;
 264				getActionBar().setDisplayHomeAsUpEnabled(false);
 265				getActionBar().setHomeButtonEnabled(false);
 266				getActionBar().setTitle(R.string.app_name);
 267				invalidateOptionsMenu();
 268				hideKeyboard();
 269			}
 270
 271			@Override
 272			public void onPanelClosed(View arg0) {
 273				paneShouldBeOpen = false;
 274				if ((conversationList.size() > 0)
 275						&& (getSelectedConversation() != null)) {
 276					getActionBar().setDisplayHomeAsUpEnabled(true);
 277					getActionBar().setHomeButtonEnabled(true);
 278					getActionBar().setTitle(
 279							getSelectedConversation().getName(useSubject));
 280					invalidateOptionsMenu();
 281					if (!getSelectedConversation().isRead()) {
 282						getSelectedConversation().markRead();
 283						UIHelper.updateNotification(getApplicationContext(),
 284								getConversationList(), null, false);
 285						listView.invalidateViews();
 286					}
 287				}
 288			}
 289
 290			@Override
 291			public void onPanelSlide(View arg0, float arg1) {
 292				// TODO Auto-generated method stub
 293
 294			}
 295		});
 296	}
 297
 298	@Override
 299	public boolean onCreateOptionsMenu(Menu menu) {
 300		getMenuInflater().inflate(R.menu.conversations, menu);
 301		MenuItem menuSecure = (MenuItem) menu.findItem(R.id.action_security);
 302		MenuItem menuArchive = (MenuItem) menu.findItem(R.id.action_archive);
 303		MenuItem menuMucDetails = (MenuItem) menu
 304				.findItem(R.id.action_muc_details);
 305		MenuItem menuContactDetails = (MenuItem) menu
 306				.findItem(R.id.action_contact_details);
 307		MenuItem menuInviteContacts = (MenuItem) menu
 308				.findItem(R.id.action_invite);
 309		MenuItem menuAttach = (MenuItem) menu.findItem(R.id.action_attach_file);
 310		MenuItem menuClearHistory = (MenuItem) menu.findItem(R.id.action_clear_history);
 311
 312		if ((spl.isOpen() && (spl.isSlideable()))) {
 313			menuArchive.setVisible(false);
 314			menuMucDetails.setVisible(false);
 315			menuContactDetails.setVisible(false);
 316			menuSecure.setVisible(false);
 317			menuInviteContacts.setVisible(false);
 318			menuAttach.setVisible(false);
 319			menuClearHistory.setVisible(false);
 320		} else {
 321			((MenuItem) menu.findItem(R.id.action_add)).setVisible(!spl
 322					.isSlideable());
 323			if (this.getSelectedConversation() != null) {
 324				if (this.getSelectedConversation().getLatestMessage()
 325						.getEncryption() != Message.ENCRYPTION_NONE) {
 326					menuSecure.setIcon(R.drawable.ic_action_secure);
 327				}
 328				if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
 329					menuContactDetails.setVisible(false);
 330					menuAttach.setVisible(false);
 331				} else {
 332					menuMucDetails.setVisible(false);
 333					menuInviteContacts.setVisible(false);
 334				}
 335			}
 336		}
 337		return true;
 338	}
 339	
 340	private void selectPresenceToAttachFile(final int attachmentChoice) {
 341		selectPresence(getSelectedConversation(), new OnPresenceSelected() {
 342			
 343			@Override
 344			public void onPresenceSelected(boolean success, String presence) {
 345				if (success) {
 346					if (attachmentChoice==ATTACHMENT_CHOICE_TAKE_PHOTO) {
 347						Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 348						takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, ImageProvider.getIncomingContentUri());
 349						if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
 350							startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
 351						}
 352					} else if (attachmentChoice==ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
 353						Intent attachFileIntent = new Intent();
 354						attachFileIntent.setType("image/*");
 355						attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
 356						Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
 357						startActivityForResult(chooser,	REQUEST_ATTACH_FILE_DIALOG);
 358					} else if (attachmentChoice==ATTACHMENT_CHOICE_RECORD_VOICE) {
 359						Intent intent = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
 360						startActivityForResult(intent, REQUEST_RECORD_AUDIO);
 361					}
 362				}
 363			}
 364
 365			@Override
 366			public void onSendPlainTextInstead() {
 367				// TODO Auto-generated method stub
 368				
 369			}
 370		},"file");
 371	}
 372
 373	private void attachFile(final int attachmentChoice) {
 374		final Conversation conversation = getSelectedConversation();
 375		if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) {
 376			if (hasPgp()) {
 377				if (conversation.getContact().getPgpKeyId()!=0) {
 378					xmppConnectionService.getPgpEngine().hasKey(conversation.getContact(), new UiCallback() {
 379						
 380						@Override
 381						public void userInputRequried(PendingIntent pi) {
 382							ConversationActivity.this.runIntent(pi, attachmentChoice);
 383						}
 384						
 385						@Override
 386						public void success() {
 387							selectPresenceToAttachFile(attachmentChoice);
 388						}
 389						
 390						@Override
 391						public void error(int error) {
 392							displayErrorDialog(error);
 393						}
 394					});
 395				} else {
 396					final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
 397							.findFragmentByTag("conversation");
 398					if (fragment != null) {
 399						fragment.showNoPGPKeyDialog(false,new OnClickListener() {
 400							
 401							@Override
 402							public void onClick(DialogInterface dialog, int which) {
 403								conversation.setNextEncryption(Message.ENCRYPTION_NONE);
 404								selectPresenceToAttachFile(attachmentChoice);
 405							}
 406						});
 407					}
 408				}
 409			}
 410		} else if (getSelectedConversation().getNextEncryption() == Message.ENCRYPTION_NONE) {
 411			selectPresenceToAttachFile(attachmentChoice);
 412		} else {
 413			AlertDialog.Builder builder = new AlertDialog.Builder(this);
 414			builder.setTitle(getString(R.string.otr_file_transfer));
 415			builder.setMessage(getString(R.string.otr_file_transfer_msg));
 416			builder.setNegativeButton(getString(R.string.cancel), null);
 417			if (conversation.getContact().getPgpKeyId()==0) {
 418				builder.setPositiveButton(getString(R.string.send_unencrypted), new OnClickListener() {
 419					
 420					@Override
 421					public void onClick(DialogInterface dialog, int which) {
 422						conversation.setNextEncryption(Message.ENCRYPTION_NONE);
 423						attachFile(attachmentChoice);
 424					}
 425				});
 426			} else {
 427				builder.setPositiveButton(getString(R.string.use_pgp_encryption), new OnClickListener() {
 428					
 429					@Override
 430					public void onClick(DialogInterface dialog, int which) {
 431						conversation.setNextEncryption(Message.ENCRYPTION_PGP);
 432						attachFile(attachmentChoice);
 433					}
 434				});
 435			}
 436			builder.create().show();
 437		}
 438	}
 439	
 440	@Override
 441	public boolean onOptionsItemSelected(MenuItem item) {
 442		switch (item.getItemId()) {
 443		case android.R.id.home:
 444			spl.openPane();
 445			break;
 446		case R.id.action_attach_file:
 447			View menuAttachFile = findViewById(R.id.action_attach_file);
 448			PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile);
 449			attachFilePopup.inflate(R.menu.attachment_choices);
 450			attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
 451				
 452				@Override
 453				public boolean onMenuItemClick(MenuItem item) {
 454					switch (item.getItemId()) {
 455					case R.id.attach_choose_picture:
 456						attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
 457						break;
 458					case R.id.attach_take_picture:
 459						attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
 460						break;
 461					case R.id.attach_record_voice:
 462						attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
 463						break;
 464					}
 465					return false;
 466				}
 467			});
 468			attachFilePopup.show();
 469			break;
 470		case R.id.action_add:
 471			startActivity(new Intent(this, ContactsActivity.class));
 472			break;
 473		case R.id.action_archive:
 474			this.endConversation(getSelectedConversation());
 475			break;
 476		case R.id.action_contact_details:
 477			Contact contact = this.getSelectedConversation().getContact();
 478			if (contact.showInRoster()) {
 479				Intent intent = new Intent(this, ContactDetailsActivity.class);
 480				intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
 481				intent.putExtra("account", this.getSelectedConversation().getAccount().getJid());
 482				intent.putExtra("contact",contact.getJid());
 483				startActivity(intent);
 484			} else {
 485				showAddToRosterDialog(getSelectedConversation());
 486			}
 487			break;
 488		case R.id.action_muc_details:
 489			Intent intent = new Intent(this, MucDetailsActivity.class);
 490			intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC);
 491			intent.putExtra("uuid", getSelectedConversation().getUuid());
 492			startActivity(intent);
 493			break;
 494		case R.id.action_invite:
 495			Intent inviteIntent = new Intent(getApplicationContext(),
 496					ContactsActivity.class);
 497			inviteIntent.setAction("invite");
 498			inviteIntent.putExtra("uuid", getSelectedConversation().getUuid());
 499			startActivity(inviteIntent);
 500			break;
 501		case R.id.action_security:
 502			final Conversation conversation = getSelectedConversation();
 503			View menuItemView = findViewById(R.id.action_security);
 504			PopupMenu popup = new PopupMenu(this, menuItemView);
 505			final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
 506					.findFragmentByTag("conversation");
 507			if (fragment != null) {
 508				popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
 509
 510					@Override
 511					public boolean onMenuItemClick(MenuItem item) {
 512						switch (item.getItemId()) {
 513						case R.id.encryption_choice_none:
 514							conversation.setNextEncryption(Message.ENCRYPTION_NONE);
 515							item.setChecked(true);
 516							break;
 517						case R.id.encryption_choice_otr:
 518							conversation.setNextEncryption(Message.ENCRYPTION_OTR);
 519							item.setChecked(true);
 520							break;
 521						case R.id.encryption_choice_pgp:
 522							if (hasPgp()) {
 523								if (conversation.getAccount().getKeys().has("pgp_signature")) {
 524									conversation.setNextEncryption(Message.ENCRYPTION_PGP);
 525									item.setChecked(true);
 526								} else {
 527									announcePgp(conversation.getAccount(),conversation);
 528								}
 529							}
 530							break;
 531						default:
 532							conversation.setNextEncryption(Message.ENCRYPTION_NONE);
 533							break;
 534						}
 535						fragment.updateChatMsgHint();
 536						return true;
 537					}
 538				});
 539				popup.inflate(R.menu.encryption_choices);
 540				MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr);
 541				if (conversation.getMode() == Conversation.MODE_MULTI) {
 542					otr.setEnabled(false);
 543				}
 544				switch (conversation.getNextEncryption()) {
 545				case Message.ENCRYPTION_NONE:
 546					popup.getMenu().findItem(R.id.encryption_choice_none)
 547							.setChecked(true);
 548					break;
 549				case Message.ENCRYPTION_OTR:
 550					otr.setChecked(true);
 551					break;
 552				case Message.ENCRYPTION_PGP:
 553					popup.getMenu().findItem(R.id.encryption_choice_pgp)
 554							.setChecked(true);
 555					break;
 556				default:
 557					popup.getMenu().findItem(R.id.encryption_choice_none)
 558							.setChecked(true);
 559					break;
 560				}
 561				popup.show();
 562			}
 563
 564			break;
 565		case R.id.action_clear_history:
 566			clearHistoryDialog(getSelectedConversation());
 567			break;
 568		default:
 569			break;
 570		}
 571		return super.onOptionsItemSelected(item);
 572	}
 573	
 574	private void endConversation(Conversation conversation) {
 575		conversation.setStatus(Conversation.STATUS_ARCHIVED);
 576		paneShouldBeOpen = true;
 577		spl.openPane();
 578		xmppConnectionService.archiveConversation(conversation);
 579		if (conversationList.size() > 0) {
 580			setSelectedConversation(conversationList.get(0));
 581		} else {
 582			setSelectedConversation(null);
 583		}
 584	}
 585
 586	protected void clearHistoryDialog(final Conversation conversation) {
 587		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 588		builder.setTitle(getString(R.string.clear_conversation_history));
 589		View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
 590		final CheckBox endConversationCheckBox = (CheckBox) dialogView.findViewById(R.id.end_conversation_checkbox);
 591		builder.setView(dialogView);
 592		builder.setNegativeButton(getString(R.string.cancel), null);
 593		builder.setPositiveButton(getString(R.string.delete_messages), new OnClickListener() {
 594			
 595			@Override
 596			public void onClick(DialogInterface dialog, int which) {
 597				activity.xmppConnectionService.clearConversationHistory(conversation);
 598				if (endConversationCheckBox.isChecked()) {
 599					endConversation(conversation);
 600				}
 601			}
 602		});
 603		builder.create().show();
 604	}
 605
 606	protected ConversationFragment swapConversationFragment() {
 607		ConversationFragment selectedFragment = new ConversationFragment();
 608
 609		FragmentTransaction transaction = getFragmentManager()
 610				.beginTransaction();
 611		transaction.replace(R.id.selected_conversation, selectedFragment,
 612				"conversation");
 613		transaction.commit();
 614		return selectedFragment;
 615	}
 616
 617	@Override
 618	public boolean onKeyDown(int keyCode, KeyEvent event) {
 619		if (keyCode == KeyEvent.KEYCODE_BACK) {
 620			if (!spl.isOpen()) {
 621				spl.openPane();
 622				return false;
 623			}
 624		}
 625		return super.onKeyDown(keyCode, event);
 626	}
 627
 628	@Override
 629	protected void onNewIntent (Intent intent) {
 630		if ((Intent.ACTION_VIEW.equals(intent.getAction())&&(VIEW_CONVERSATION.equals(intent.getType())))) {
 631			String convToView = (String) intent.getExtras().get(
 632					CONVERSATION);
 633
 634			for (int i = 0; i < conversationList.size(); ++i) {
 635				if (conversationList.get(i).getUuid().equals(convToView)) {
 636					setSelectedConversation(conversationList.get(i));
 637				}
 638			}
 639			paneShouldBeOpen = false;
 640			String text = intent.getExtras().getString(TEXT, null);
 641			swapConversationFragment().setText(text);
 642		}
 643	}
 644	
 645	@Override
 646	public void onStart() {
 647		super.onStart();
 648		SharedPreferences preferences = PreferenceManager
 649				.getDefaultSharedPreferences(this);
 650		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
 651		if (this.xmppConnectionServiceBound) {
 652			this.onBackendConnected();
 653		}
 654		if (conversationList.size() >= 1) {
 655			onConvChanged.onConversationListChanged();
 656		}
 657	}
 658
 659	@Override
 660	protected void onStop() {
 661		if (xmppConnectionServiceBound) {
 662			xmppConnectionService.removeOnConversationListChangedListener();
 663		}
 664		super.onStop();
 665	}
 666
 667	@Override
 668	void onBackendConnected() {
 669		this.registerListener();
 670		if (conversationList.size() == 0) {
 671			updateConversationList();
 672		}
 673
 674		if ((getIntent().getAction() != null)
 675				&& (getIntent().getAction().equals(Intent.ACTION_VIEW) && (!handledViewIntent))) {
 676			if (getIntent().getType().equals(
 677					ConversationActivity.VIEW_CONVERSATION)) {
 678				handledViewIntent = true;
 679
 680				String convToView = (String) getIntent().getExtras().get(
 681						CONVERSATION);
 682
 683				for (int i = 0; i < conversationList.size(); ++i) {
 684					if (conversationList.get(i).getUuid().equals(convToView)) {
 685						setSelectedConversation(conversationList.get(i));
 686					}
 687				}
 688				paneShouldBeOpen = false;
 689				String text = getIntent().getExtras().getString(TEXT, null);
 690				swapConversationFragment().setText(text);
 691			}
 692		} else {
 693			if (xmppConnectionService.getAccounts().size() == 0) {
 694				startActivity(new Intent(this, ManageAccountActivity.class));
 695				finish();
 696			} else if (conversationList.size() <= 0) {
 697				// add no history
 698				startActivity(new Intent(this, ContactsActivity.class));
 699				finish();
 700			} else {
 701				spl.openPane();
 702				// find currently loaded fragment
 703				ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
 704						.findFragmentByTag("conversation");
 705				if (selectedFragment != null) {
 706					selectedFragment.onBackendConnected();
 707				} else {
 708					setSelectedConversation(conversationList.get(0));
 709					swapConversationFragment();
 710				}
 711				ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
 712			}
 713		}
 714	}
 715
 716	public void registerListener() {
 717		if (xmppConnectionServiceBound) {
 718			xmppConnectionService
 719					.setOnConversationListChangedListener(this.onConvChanged);
 720		}
 721	}
 722
 723	@Override
 724	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
 725		super.onActivityResult(requestCode, resultCode, data);
 726		if (resultCode == RESULT_OK) {
 727			if (requestCode == REQUEST_DECRYPT_PGP) {
 728				ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
 729						.findFragmentByTag("conversation");
 730				if (selectedFragment != null) {
 731					selectedFragment.hidePgpPassphraseBox();
 732				}
 733			} else if (requestCode == REQUEST_ATTACH_FILE_DIALOG) {
 734				attachImageToConversation(getSelectedConversation(),data.getData());
 735			} else if (requestCode == REQUEST_SEND_PGP_IMAGE) {
 736				
 737			} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
 738				attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
 739			} else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
 740				attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
 741			} else if (requestCode == REQUEST_ANNOUNCE_PGP) {
 742				announcePgp(getSelectedConversation().getAccount(),getSelectedConversation());
 743			} else if (requestCode == REQUEST_ENCRYPT_MESSAGE) {
 744				encryptTextMessage();
 745			} else if (requestCode == REQUEST_IMAGE_CAPTURE) {
 746				attachImageToConversation(getSelectedConversation(), null);
 747			} else if (requestCode == REQUEST_RECORD_AUDIO) {
 748				Log.d("xmppService",data.getData().toString());
 749				attachAudioToConversation(getSelectedConversation(),data.getData());
 750			} else {
 751				Log.d(LOGTAG,"unknown result code:"+requestCode);
 752			}
 753		}
 754	}
 755	
 756	private void attachAudioToConversation(Conversation conversation, Uri uri) {
 757		
 758	}
 759
 760	private void attachImageToConversation(Conversation conversation, Uri uri) {
 761		prepareImageToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
 762		prepareImageToast.show();
 763		pendingMessage = xmppConnectionService.attachImageToConversation(conversation, uri, new UiCallback() {
 764				
 765			@Override
 766			public void userInputRequried(PendingIntent pi) {
 767				hidePrepareImageToast();
 768				ConversationActivity.this.runIntent(pi, ConversationActivity.REQUEST_SEND_PGP_IMAGE);
 769			}
 770			
 771			@Override
 772			public void success() {
 773				sendPendingImageMessage();
 774				hidePrepareImageToast();
 775			}
 776			
 777			@Override
 778			public void error(int error) {
 779				hidePrepareImageToast();
 780				pendingMessage = null;
 781				displayErrorDialog(error);
 782			}
 783		});
 784	}
 785	
 786	private void hidePrepareImageToast() {
 787		if (prepareImageToast!=null) {
 788			runOnUiThread(new Runnable() {
 789				
 790				@Override
 791				public void run() {
 792					prepareImageToast.cancel();
 793				}
 794			});
 795		}
 796	}
 797
 798	private void sendPendingImageMessage() {
 799		pendingMessage.getConversation().getMessages().add(pendingMessage);
 800		xmppConnectionService.databaseBackend.createMessage(pendingMessage);
 801		xmppConnectionService.sendMessage(pendingMessage, null);
 802		xmppConnectionService.updateUi(pendingMessage.getConversation(), false);
 803		pendingMessage = null;
 804	}
 805	
 806	public void updateConversationList() {
 807		conversationList.clear();
 808		conversationList.addAll(xmppConnectionService.getConversations());
 809		listView.invalidateViews();
 810	}
 811	
 812	public void selectPresence(final Conversation conversation, final OnPresenceSelected listener, String reason) {
 813		Account account = conversation.getAccount();
 814		if (account.getStatus() != Account.STATUS_ONLINE) {
 815			AlertDialog.Builder builder = new AlertDialog.Builder(this);
 816			builder.setTitle(getString(R.string.not_connected));
 817			builder.setIconAttribute(android.R.attr.alertDialogIcon);
 818			if ("otr".equals(reason)) {
 819				builder.setMessage(getString(R.string.you_are_offline,getString(R.string.otr_messages)));
 820			} else if ("file".equals(reason)) {
 821				builder.setMessage(getString(R.string.you_are_offline,getString(R.string.files)));
 822			} else {
 823				builder.setMessage(getString(R.string.you_are_offline_blank));
 824			}
 825			builder.setNegativeButton(getString(R.string.cancel), null);
 826			builder.setPositiveButton(getString(R.string.manage_account), new OnClickListener() {
 827				
 828				@Override
 829				public void onClick(DialogInterface dialog, int which) {
 830					startActivity(new Intent(activity, ManageAccountActivity.class));
 831				}
 832			});
 833			builder.create().show();
 834			listener.onPresenceSelected(false, null);
 835		} else {
 836			Contact contact = conversation.getContact();
 837			if (contact==null) {
 838				showAddToRosterDialog(conversation);
 839				listener.onPresenceSelected(false,null);
 840			} else {
 841				Hashtable<String, Integer> presences = contact.getPresences();
 842				if (presences.size() == 0) {
 843					AlertDialog.Builder builder = new AlertDialog.Builder(this);
 844					builder.setTitle(getString(R.string.contact_offline));
 845					if ("otr".equals(reason)) {
 846						builder.setMessage(getString(R.string.contact_offline_otr));
 847						builder.setPositiveButton(getString(R.string.send_unencrypted), new OnClickListener() {
 848							
 849							@Override
 850							public void onClick(DialogInterface dialog, int which) {
 851								listener.onSendPlainTextInstead();
 852							}
 853						});
 854					} else if ("file".equals(reason)) {
 855						builder.setMessage(getString(R.string.contact_offline_file));
 856					}
 857					builder.setIconAttribute(android.R.attr.alertDialogIcon);
 858					builder.setNegativeButton(getString(R.string.cancel), null);
 859					builder.create().show();
 860					listener.onPresenceSelected(false, null);
 861				} else if (presences.size() == 1) {
 862					String presence = (String) presences.keySet().toArray()[0];
 863					conversation.setNextPresence(presence);
 864					listener.onPresenceSelected(true, presence);
 865				} else {
 866					AlertDialog.Builder builder = new AlertDialog.Builder(this);
 867					builder.setTitle(getString(R.string.choose_presence));
 868					final String[] presencesArray = new String[presences.size()];
 869					presences.keySet().toArray(presencesArray);
 870					builder.setItems(presencesArray,
 871							new DialogInterface.OnClickListener() {
 872	
 873								@Override
 874								public void onClick(DialogInterface dialog,
 875										int which) {
 876									String presence = presencesArray[which];
 877									conversation.setNextPresence(presence);
 878									listener.onPresenceSelected(true,presence);
 879								}
 880							});
 881					builder.create().show();
 882				}
 883			}
 884		}
 885	}
 886	
 887	private void showAddToRosterDialog(final Conversation conversation) {
 888		String jid = conversation.getContactJid();
 889		AlertDialog.Builder builder = new AlertDialog.Builder(this);
 890		builder.setTitle(jid);
 891		builder.setMessage(getString(R.string.not_in_roster));
 892		builder.setNegativeButton(getString(R.string.cancel), null);
 893		builder.setPositiveButton(getString(R.string.add_contact), new DialogInterface.OnClickListener() {
 894
 895			@Override
 896			public void onClick(DialogInterface dialog, int which) {
 897				String jid = conversation.getContactJid();
 898				Account account = getSelectedConversation().getAccount();
 899				Contact contact = account.getRoster().getContact(jid);
 900				xmppConnectionService.createContact(contact);
 901			}
 902		});
 903		builder.create().show();
 904	}
 905	
 906	public void runIntent(PendingIntent pi, int requestCode) {
 907		try {
 908			this.startIntentSenderForResult(pi.getIntentSender(),requestCode, null, 0,
 909					0, 0);
 910		} catch (SendIntentException e1) {
 911			Log.d("xmppService","failed to start intent to send message");
 912		}
 913	}
 914	
 915	
 916	class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
 917	    private final WeakReference<ImageView> imageViewReference;
 918	    private Message message = null;
 919
 920	    public BitmapWorkerTask(ImageView imageView) {
 921	        imageViewReference = new WeakReference<ImageView>(imageView);
 922	    }
 923
 924	    @Override
 925	    protected Bitmap doInBackground(Message... params) {
 926	        message = params[0];
 927	        try {
 928				return xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288),false);
 929			} catch (FileNotFoundException e) {
 930				Log.d("xmppService","file not found!");
 931				return null;
 932			}
 933	    }
 934
 935	    @Override
 936	    protected void onPostExecute(Bitmap bitmap) {
 937	        if (imageViewReference != null && bitmap != null) {
 938	            final ImageView imageView = imageViewReference.get();
 939	            if (imageView != null) {
 940	                imageView.setImageBitmap(bitmap);
 941	                imageView.setBackgroundColor(0x00000000);
 942	            }
 943	        }
 944	    }
 945	}
 946	
 947	public void loadBitmap(Message message, ImageView imageView) {
 948		Bitmap bm;
 949		try {
 950			bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true);
 951		} catch (FileNotFoundException e) {
 952			bm = null;
 953		}
 954		if (bm!=null) {
 955			imageView.setImageBitmap(bm);
 956			imageView.setBackgroundColor(0x00000000);
 957		} else {
 958		    if (cancelPotentialWork(message, imageView)) {
 959		    	imageView.setBackgroundColor(0xff333333);
 960		        final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
 961		        final AsyncDrawable asyncDrawable =
 962		                new AsyncDrawable(getResources(), null, task);
 963		        imageView.setImageDrawable(asyncDrawable);
 964		        task.execute(message);
 965		    }
 966		}
 967	}
 968	
 969	public static boolean cancelPotentialWork(Message message, ImageView imageView) {
 970	    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
 971
 972	    if (bitmapWorkerTask != null) {
 973	        final Message oldMessage = bitmapWorkerTask.message;
 974	        if (oldMessage == null || message != oldMessage) {
 975	            bitmapWorkerTask.cancel(true);
 976	        } else {
 977	            return false;
 978	        }
 979	    }
 980	    return true;
 981	}
 982	
 983	private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
 984		   if (imageView != null) {
 985		       final Drawable drawable = imageView.getDrawable();
 986		       if (drawable instanceof AsyncDrawable) {
 987		           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
 988		           return asyncDrawable.getBitmapWorkerTask();
 989		       }
 990		    }
 991		    return null;
 992	}
 993	
 994	static class AsyncDrawable extends BitmapDrawable {
 995	    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
 996
 997	    public AsyncDrawable(Resources res, Bitmap bitmap,
 998	            BitmapWorkerTask bitmapWorkerTask) {
 999	        super(res, bitmap);
1000	        bitmapWorkerTaskReference =
1001	            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
1002	    }
1003
1004	    public BitmapWorkerTask getBitmapWorkerTask() {
1005	        return bitmapWorkerTaskReference.get();
1006	    }
1007	}
1008
1009	public void encryptTextMessage() {
1010		xmppConnectionService.getPgpEngine().encrypt(this.pendingMessage, new UiCallback() {
1011
1012					@Override
1013					public void userInputRequried(
1014							PendingIntent pi) {
1015						activity.runIntent(
1016								pi,
1017								ConversationActivity.REQUEST_SEND_MESSAGE);
1018					}
1019
1020					@Override
1021					public void success() {
1022						xmppConnectionService.sendMessage(pendingMessage, null);
1023						pendingMessage = null;
1024						ConversationFragment selectedFragment = (ConversationFragment) getFragmentManager()
1025								.findFragmentByTag("conversation");
1026						if (selectedFragment != null) {
1027							selectedFragment.clearInputField();
1028						}
1029					}
1030
1031					@Override
1032					public void error(int error) {
1033
1034					}
1035				});
1036	}
1037}