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