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