ConversationActivity.java

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