ConversationActivity.java

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