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