ConversationFragment.java

  1package eu.siacs.conversations.ui;
  2
  3import java.io.FileNotFoundException;
  4import java.util.ArrayList;
  5import java.util.HashMap;
  6import java.util.Hashtable;
  7import java.util.LinkedList;
  8import java.util.List;
  9import java.util.Set;
 10
 11import net.java.otr4j.session.SessionStatus;
 12
 13import eu.siacs.conversations.R;
 14import eu.siacs.conversations.crypto.PgpEngine.OpenPgpException;
 15import eu.siacs.conversations.crypto.PgpEngine.UserInputRequiredException;
 16import eu.siacs.conversations.entities.Account;
 17import eu.siacs.conversations.entities.Contact;
 18import eu.siacs.conversations.entities.Conversation;
 19import eu.siacs.conversations.entities.Message;
 20import eu.siacs.conversations.entities.MucOptions;
 21import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
 22import eu.siacs.conversations.services.XmppConnectionService;
 23import eu.siacs.conversations.utils.UIHelper;
 24import eu.siacs.conversations.xmpp.jingle.JingleConnection;
 25import android.app.AlertDialog;
 26import android.app.Fragment;
 27import android.content.Context;
 28import android.content.DialogInterface;
 29import android.content.Intent;
 30import android.content.IntentSender;
 31import android.content.SharedPreferences;
 32import android.content.IntentSender.SendIntentException;
 33import android.graphics.Bitmap;
 34import android.graphics.Typeface;
 35import android.net.Uri;
 36import android.os.AsyncTask;
 37import android.os.Bundle;
 38import android.preference.PreferenceManager;
 39import android.util.DisplayMetrics;
 40import android.util.Log;
 41import android.view.LayoutInflater;
 42import android.view.View;
 43import android.view.View.OnClickListener;
 44import android.view.ViewGroup;
 45import android.widget.ArrayAdapter;
 46import android.widget.Button;
 47import android.widget.EditText;
 48import android.widget.LinearLayout;
 49import android.widget.ListView;
 50import android.widget.ImageButton;
 51import android.widget.ImageView;
 52import android.widget.TextView;
 53import android.widget.Toast;
 54
 55public class ConversationFragment extends Fragment {
 56
 57	protected Conversation conversation;
 58	protected ListView messagesView;
 59	protected LayoutInflater inflater;
 60	protected List<Message> messageList = new ArrayList<Message>();
 61	protected ArrayAdapter<Message> messageListAdapter;
 62	protected Contact contact;
 63	protected BitmapCache mBitmapCache = new BitmapCache();
 64
 65	protected String queuedPqpMessage = null;
 66
 67	private EditText chatMsg;
 68	private String pastedText = null;
 69
 70	protected Bitmap selfBitmap;
 71	
 72	private boolean useSubject = true;
 73
 74	private IntentSender askForPassphraseIntent = null;
 75
 76	private OnClickListener sendMsgListener = new OnClickListener() {
 77
 78		@Override
 79		public void onClick(View v) {
 80			if (chatMsg.getText().length() < 1)
 81				return;
 82			Message message = new Message(conversation, chatMsg.getText()
 83					.toString(), conversation.nextMessageEncryption);
 84			if (conversation.nextMessageEncryption == Message.ENCRYPTION_OTR) {
 85				sendOtrMessage(message);
 86			} else if (conversation.nextMessageEncryption == Message.ENCRYPTION_PGP) {
 87				sendPgpMessage(message);
 88			} else {
 89				sendPlainTextMessage(message);
 90			}
 91		}
 92	};
 93	protected OnClickListener clickToDecryptListener = new OnClickListener() {
 94
 95		@Override
 96		public void onClick(View v) {
 97			if (askForPassphraseIntent != null) {
 98				try {
 99					getActivity().startIntentSenderForResult(
100							askForPassphraseIntent,
101							ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
102							0, 0);
103				} catch (SendIntentException e) {
104					Log.d("xmppService", "couldnt fire intent");
105				}
106			}
107		}
108	};
109
110	private LinearLayout pgpInfo;
111	private LinearLayout mucError;
112	private TextView mucErrorText;
113	private OnClickListener clickToMuc = new OnClickListener() {
114
115		@Override
116		public void onClick(View v) {
117			Intent intent = new Intent(getActivity(), MucDetailsActivity.class);
118			intent.setAction(MucDetailsActivity.ACTION_VIEW_MUC);
119			intent.putExtra("uuid", conversation.getUuid());
120			startActivity(intent);
121		}
122	};
123	private ConversationActivity activity;
124
125	public void hidePgpPassphraseBox() {
126		pgpInfo.setVisibility(View.GONE);
127	}
128
129	public void updateChatMsgHint() {
130		if (conversation.getMode() == Conversation.MODE_MULTI) {
131			chatMsg.setHint(getString(R.string.send_message_to_conference));
132		} else {
133			switch (conversation.nextMessageEncryption) {
134			case Message.ENCRYPTION_NONE:
135				chatMsg.setHint(getString(R.string.send_plain_text_message));
136				break;
137			case Message.ENCRYPTION_OTR:
138				chatMsg.setHint(getString(R.string.send_otr_message));
139				break;
140			case Message.ENCRYPTION_PGP:
141				chatMsg.setHint(getString(R.string.send_pgp_message));
142				break;
143			case Message.ENCRYPTION_DECRYPTED:
144				chatMsg.setHint(getString(R.string.send_pgp_message));
145				break;
146			default:
147				break;
148			}
149		}
150	}
151
152	@Override
153	public View onCreateView(final LayoutInflater inflater,
154			ViewGroup container, Bundle savedInstanceState) {
155
156		final DisplayMetrics metrics = getResources().getDisplayMetrics();
157		
158		this.inflater = inflater;
159
160		final View view = inflater.inflate(R.layout.fragment_conversation,
161				container, false);
162		chatMsg = (EditText) view.findViewById(R.id.textinput);
163		
164		if (pastedText!=null) {
165			chatMsg.setText(pastedText);
166		}
167		
168		ImageButton sendButton = (ImageButton) view
169				.findViewById(R.id.textSendButton);
170		sendButton.setOnClickListener(this.sendMsgListener);
171
172		pgpInfo = (LinearLayout) view.findViewById(R.id.pgp_keyentry);
173		pgpInfo.setOnClickListener(clickToDecryptListener);
174		mucError = (LinearLayout) view.findViewById(R.id.muc_error);
175		mucError.setOnClickListener(clickToMuc);
176		mucErrorText = (TextView) view.findViewById(R.id.muc_error_msg);
177
178		messagesView = (ListView) view.findViewById(R.id.messages_view);
179
180		messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
181				.getApplicationContext(), R.layout.message_sent,
182				this.messageList) {
183
184			private static final int SENT = 0;
185			private static final int RECIEVED = 1;
186
187			@Override
188			public int getViewTypeCount() {
189				return 2;
190			}
191
192			@Override
193			public int getItemViewType(int position) {
194				if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
195					return RECIEVED;
196				} else {
197					return SENT;
198				}
199			}
200
201			@Override
202			public View getView(int position, View view, ViewGroup parent) {
203				final Message item = getItem(position);
204				int type = getItemViewType(position);
205				ViewHolder viewHolder;
206				if (view == null) {
207					viewHolder = new ViewHolder();
208					switch (type) {
209					case SENT:
210						view = (View) inflater.inflate(R.layout.message_sent,
211								null);
212						viewHolder.contact_picture = (ImageView) view
213								.findViewById(R.id.message_photo);
214						viewHolder.contact_picture.setImageBitmap(selfBitmap);
215						break;
216					case RECIEVED:
217						view = (View) inflater.inflate(
218								R.layout.message_recieved, null);
219						viewHolder.contact_picture = (ImageView) view
220								.findViewById(R.id.message_photo);
221						
222						viewHolder.download_button = (Button) view.findViewById(R.id.download_button);
223						
224						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
225
226							viewHolder.contact_picture.setImageBitmap(mBitmapCache
227									.get(item.getConversation().getName(useSubject), item
228											.getConversation().getContact(),
229											getActivity()
230													.getApplicationContext()));
231
232						}
233						break;
234					default:
235						viewHolder = null;
236						break;
237					}
238					viewHolder.indicator = (ImageView) view.findViewById(R.id.security_indicator);
239					viewHolder.image = (ImageView) view.findViewById(R.id.message_image);
240					viewHolder.messageBody = (TextView) view
241							.findViewById(R.id.message_body);
242					viewHolder.time = (TextView) view
243							.findViewById(R.id.message_time);
244					view.setTag(viewHolder);
245				} else {
246					viewHolder = (ViewHolder) view.getTag();
247				}
248				if (type == RECIEVED) {
249					if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
250						if (item.getCounterpart() != null) {
251							viewHolder.contact_picture.setImageBitmap(mBitmapCache
252									.get(item.getCounterpart(), null,
253											getActivity()
254													.getApplicationContext()));
255						} else {
256							viewHolder.contact_picture.setImageBitmap(mBitmapCache
257									.get(item.getConversation().getName(useSubject),
258											null, getActivity()
259													.getApplicationContext()));
260						}
261					}
262				}
263				
264				if (item.getEncryption() == Message.ENCRYPTION_NONE) {
265					viewHolder.indicator.setVisibility(View.GONE);
266				} else {
267					viewHolder.indicator.setVisibility(View.VISIBLE);
268				}
269				
270				
271				if (item.getType() == Message.TYPE_IMAGE) {
272					if ((item.getStatus() == Message.STATUS_PREPARING)||(item.getStatus() == Message.STATUS_RECIEVING)) {
273						viewHolder.image.setVisibility(View.GONE);
274						viewHolder.messageBody.setVisibility(View.VISIBLE);
275						if (item.getStatus() == Message.STATUS_PREPARING) {
276							viewHolder.messageBody.setText(getString(R.string.preparing_image));
277						} else if (item.getStatus() == Message.STATUS_RECIEVING) {
278							viewHolder.download_button.setVisibility(View.GONE);
279							viewHolder.messageBody.setText(getString(R.string.receiving_image));
280						}
281						viewHolder.messageBody.setTextColor(0xff33B5E5);
282						viewHolder.messageBody.setTypeface(null,Typeface.ITALIC);
283					} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
284						viewHolder.image.setVisibility(View.GONE);
285						viewHolder.messageBody.setVisibility(View.GONE);
286						viewHolder.download_button.setVisibility(View.VISIBLE);
287						viewHolder.download_button.setOnClickListener(new OnClickListener() {
288							
289							@Override
290							public void onClick(View v) {
291								JingleConnection connection = item.getJingleConnection();
292								if (connection!=null) {
293									connection.accept();
294								} else {
295									Log.d("xmppService","attached jingle connection was null");
296								}
297							}
298						});
299					} else {
300						try {
301							Bitmap thumbnail = activity.xmppConnectionService.getFileBackend().getThumbnailFromMessage(item,(int) (metrics.density * 288));
302							viewHolder.image.setImageBitmap(thumbnail);
303							viewHolder.messageBody.setVisibility(View.GONE);
304							viewHolder.image.setVisibility(View.VISIBLE);
305							viewHolder.image.setOnClickListener(new OnClickListener() {
306								
307								@Override
308								public void onClick(View v) {
309									Uri uri = Uri.parse("content://eu.siacs.conversations.images/"+item.getConversationUuid()+"/"+item.getUuid());
310									Log.d("xmppService","staring intent with uri:"+uri.toString());
311									Intent intent = new Intent(Intent.ACTION_VIEW);
312								    intent.setDataAndType(uri, "image/*");
313								    startActivity(intent);
314								}
315							});
316						} catch (FileNotFoundException e) {
317							viewHolder.image.setVisibility(View.GONE);
318							viewHolder.messageBody.setText("error loading image file");
319							viewHolder.messageBody.setVisibility(View.VISIBLE);
320						}
321					}
322				} else {
323					viewHolder.image.setVisibility(View.GONE);
324					viewHolder.messageBody.setVisibility(View.VISIBLE);
325					String body = item.getBody();
326					if (body != null) {
327						if (item.getEncryption() == Message.ENCRYPTION_PGP) {
328							viewHolder.messageBody
329									.setText(getString(R.string.encrypted_message));
330							viewHolder.messageBody.setTextColor(0xff33B5E5);
331							viewHolder.messageBody.setTypeface(null,
332									Typeface.ITALIC);
333						} else if ((item.getEncryption() == Message.ENCRYPTION_OTR)||(item.getEncryption() == Message.ENCRYPTION_DECRYPTED)) {
334							viewHolder.messageBody.setText(body.trim());
335							viewHolder.messageBody.setTextColor(0xff333333);
336							viewHolder.messageBody.setTypeface(null,
337									Typeface.NORMAL);
338						} else {
339							viewHolder.messageBody.setText(body.trim());
340							viewHolder.messageBody.setTextColor(0xff333333);
341							viewHolder.messageBody.setTypeface(null,
342									Typeface.NORMAL);
343						}
344					}
345				}
346				switch (item.getStatus()) {
347				case Message.STATUS_UNSEND:
348					viewHolder.time.setTypeface(null, Typeface.ITALIC);
349					viewHolder.time.setTextColor(0xFF8e8e8e);
350					viewHolder.time.setText("sending\u2026");
351					break;
352				case Message.STATUS_OFFERED:
353					viewHolder.time.setTypeface(null, Typeface.ITALIC);
354					viewHolder.time.setTextColor(0xFF8e8e8e);
355					viewHolder.time.setText("offering\u2026");
356					break;
357				case Message.STATUS_SEND_FAILED:
358					viewHolder.time.setText(getString(R.string.send_failed) + " \u00B7 " + UIHelper.readableTimeDifference(item
359							.getTimeSent()));
360					viewHolder.time.setTextColor(0xFFe92727);
361					viewHolder.time.setTypeface(null,Typeface.NORMAL);
362					break;
363				case Message.STATUS_SEND_REJECTED:
364					viewHolder.time.setText(getString(R.string.send_rejected));
365					viewHolder.time.setTextColor(0xFFe92727);
366					viewHolder.time.setTypeface(null,Typeface.NORMAL);
367					break;
368				default:
369					viewHolder.time.setTypeface(null, Typeface.NORMAL);
370					viewHolder.time.setTextColor(0xFF8e8e8e);
371					if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
372						viewHolder.time.setText(UIHelper
373								.readableTimeDifference(item.getTimeSent()));
374					} else {
375						viewHolder.time.setText(item.getCounterpart()
376								+ " \u00B7 "
377								+ UIHelper.readableTimeDifference(item
378										.getTimeSent()));
379					}
380					break;
381				}
382				return view;
383			}
384		};
385		messagesView.setAdapter(messageListAdapter);
386
387		return view;
388	}
389
390	protected Bitmap findSelfPicture() {
391		SharedPreferences sharedPref = PreferenceManager
392				.getDefaultSharedPreferences(getActivity()
393						.getApplicationContext());
394		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
395				"show_phone_selfcontact_picture", true);
396
397		return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
398				showPhoneSelfContactPicture, getActivity());
399	}
400
401	@Override
402	public void onStart() {
403		super.onStart();
404		this.activity = (ConversationActivity) getActivity();
405		SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
406		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
407		if (activity.xmppConnectionServiceBound) {
408			this.onBackendConnected();
409		}
410	}
411
412	public void onBackendConnected() {
413		this.conversation = activity.getSelectedConversation();
414		if (this.conversation == null) {
415			return;
416		}
417		this.selfBitmap = findSelfPicture();
418		updateMessages();
419		// rendering complete. now go tell activity to close pane
420		if (activity.getSlidingPaneLayout().isSlideable()) {
421			if (!activity.shouldPaneBeOpen()) {
422				activity.getSlidingPaneLayout().closePane();
423				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
424				activity.getActionBar().setTitle(conversation.getName(useSubject));
425				activity.invalidateOptionsMenu();
426
427			}
428		}
429		if (queuedPqpMessage != null) {
430			this.conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
431			Message message = new Message(conversation, queuedPqpMessage,
432					Message.ENCRYPTION_PGP);
433			sendPgpMessage(message);
434		}
435		if (conversation.getMode() == Conversation.MODE_MULTI) {
436			activity.xmppConnectionService
437					.setOnRenameListener(new OnRenameListener() {
438
439						@Override
440						public void onRename(final boolean success) {
441							activity.xmppConnectionService.updateConversation(conversation);
442							getActivity().runOnUiThread(new Runnable() {
443
444								@Override
445								public void run() {
446									if (success) {
447										Toast.makeText(
448												getActivity(),
449												getString(R.string.your_nick_has_been_changed),
450												Toast.LENGTH_SHORT).show();
451									} else {
452										Toast.makeText(getActivity(),
453												getString(R.string.nick_in_use),
454												Toast.LENGTH_SHORT).show();
455									}
456								}
457							});
458						}
459					});
460		}
461	}
462
463	public void updateMessages() {
464		ConversationActivity activity = (ConversationActivity) getActivity();
465		if (this.conversation != null) {
466			List<Message> encryptedMessages = new LinkedList<Message>();
467			for (Message message : this.conversation.getMessages()) {
468				if (message.getEncryption() == Message.ENCRYPTION_PGP) {
469					encryptedMessages.add(message);
470				}
471			}
472			if (encryptedMessages.size() > 0) {
473				DecryptMessage task = new DecryptMessage();
474				Message[] msgs = new Message[encryptedMessages.size()];
475				task.execute(encryptedMessages.toArray(msgs));
476			}
477			this.messageList.clear();
478			this.messageList.addAll(this.conversation.getMessages());
479			this.messageListAdapter.notifyDataSetChanged();
480			if (conversation.getMode() == Conversation.MODE_SINGLE) {
481				if (messageList.size() >= 1) {
482					int latestEncryption = this.conversation.getLatestMessage()
483							.getEncryption();
484					if (latestEncryption == Message.ENCRYPTION_DECRYPTED) {
485						conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
486					} else {
487						conversation.nextMessageEncryption = latestEncryption;
488					}
489					makeFingerprintWarning(latestEncryption);
490				}
491			} else {
492				if (conversation.getMucOptions().getError() != 0) {
493					mucError.setVisibility(View.VISIBLE);
494					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
495						mucErrorText.setText(getString(R.string.nick_in_use));
496					}
497				} else {
498					mucError.setVisibility(View.GONE);
499				}
500			}
501			getActivity().invalidateOptionsMenu();
502			updateChatMsgHint();
503			int size = this.messageList.size();
504			if (size >= 1)
505				messagesView.setSelection(size - 1);
506			if (!activity.shouldPaneBeOpen()) {
507				conversation.markRead();
508				// TODO update notifications
509				UIHelper.updateNotification(getActivity(),
510						activity.getConversationList(), null, false);
511				activity.updateConversationList();
512			}
513		}
514	}
515
516	protected void makeFingerprintWarning(int latestEncryption) {
517		final LinearLayout fingerprintWarning = (LinearLayout) getView()
518				.findViewById(R.id.new_fingerprint);
519		if (conversation.getContact() != null) {
520			Set<String> knownFingerprints = conversation.getContact()
521					.getOtrFingerprints();
522			if ((latestEncryption == Message.ENCRYPTION_OTR)
523					&& (conversation.hasValidOtrSession()
524							&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
525								.contains(conversation.getOtrFingerprint())))) {
526				fingerprintWarning.setVisibility(View.VISIBLE);
527				TextView fingerprint = (TextView) getView().findViewById(
528						R.id.otr_fingerprint);
529				fingerprint.setText(conversation.getOtrFingerprint());
530				fingerprintWarning.setOnClickListener(new OnClickListener() {
531
532					@Override
533					public void onClick(View v) {
534						AlertDialog dialog = UIHelper
535								.getVerifyFingerprintDialog(
536										(ConversationActivity) getActivity(),
537										conversation, fingerprintWarning);
538						dialog.show();
539					}
540				});
541			} else {
542				fingerprintWarning.setVisibility(View.GONE);
543			}
544		} else {
545			fingerprintWarning.setVisibility(View.GONE);
546		}
547	}
548
549	protected void sendPlainTextMessage(Message message) {
550		ConversationActivity activity = (ConversationActivity) getActivity();
551		activity.xmppConnectionService.sendMessage(message, null);
552		chatMsg.setText("");
553	}
554
555	protected void sendPgpMessage(final Message message) {
556		ConversationActivity activity = (ConversationActivity) getActivity();
557		final XmppConnectionService xmppService = activity.xmppConnectionService;
558		Contact contact = message.getConversation().getContact();
559		Account account = message.getConversation().getAccount();
560		if (activity.hasPgp()) {
561			if (contact.getPgpKeyId() != 0) {
562				try {
563					message.setEncryptedBody(xmppService.getPgpEngine().encrypt(account, contact.getPgpKeyId(), message.getBody()));
564					xmppService.sendMessage(message, null);
565					chatMsg.setText("");
566				} catch (UserInputRequiredException e) {
567					try {
568						getActivity().startIntentSenderForResult(e.getPendingIntent().getIntentSender(),
569								ConversationActivity.REQUEST_SEND_MESSAGE, null, 0,
570								0, 0);
571					} catch (SendIntentException e1) {
572						Log.d("xmppService","failed to start intent to send message");
573					}
574				} catch (OpenPgpException e) {
575					Log.d("xmppService","error encrypting with pgp: "+e.getOpenPgpError().getMessage());
576				}
577			} else {
578				AlertDialog.Builder builder = new AlertDialog.Builder(
579						getActivity());
580				builder.setTitle("No openPGP key found");
581				builder.setIconAttribute(android.R.attr.alertDialogIcon);
582				builder.setMessage("There is no openPGP key associated with this contact");
583				builder.setNegativeButton("Cancel", null);
584				builder.setPositiveButton("Send plain text",
585						new DialogInterface.OnClickListener() {
586
587							@Override
588							public void onClick(DialogInterface dialog,
589									int which) {
590								conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
591								message.setEncryption(Message.ENCRYPTION_NONE);
592								xmppService.sendMessage(message, null);
593								chatMsg.setText("");
594							}
595						});
596				builder.create().show();
597			}
598		}
599	}
600
601	protected void sendOtrMessage(final Message message) {
602		ConversationActivity activity = (ConversationActivity) getActivity();
603		final XmppConnectionService xmppService = activity.xmppConnectionService;
604		if (conversation.hasValidOtrSession()) {
605			activity.xmppConnectionService.sendMessage(message, null);
606			chatMsg.setText("");
607		} else {
608			Hashtable<String, Integer> presences;
609			if (conversation.getContact() != null) {
610				presences = conversation.getContact().getPresences();
611			} else {
612				presences = null;
613			}
614			if ((presences == null) || (presences.size() == 0)) {
615				AlertDialog.Builder builder = new AlertDialog.Builder(
616						getActivity());
617				builder.setTitle("Contact is offline");
618				builder.setIconAttribute(android.R.attr.alertDialogIcon);
619				builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
620				builder.setPositiveButton("Send plain text",
621						new DialogInterface.OnClickListener() {
622
623							@Override
624							public void onClick(DialogInterface dialog,
625									int which) {
626								conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
627								message.setEncryption(Message.ENCRYPTION_NONE);
628								xmppService.sendMessage(message, null);
629								chatMsg.setText("");
630							}
631						});
632				builder.setNegativeButton("Cancel", null);
633				builder.create().show();
634			} else if (presences.size() == 1) {
635				xmppService.sendMessage(message, (String) presences.keySet()
636						.toArray()[0]);
637				chatMsg.setText("");
638			} else {
639				AlertDialog.Builder builder = new AlertDialog.Builder(
640						getActivity());
641				builder.setTitle("Choose Presence");
642				final String[] presencesArray = new String[presences.size()];
643				presences.keySet().toArray(presencesArray);
644				builder.setItems(presencesArray,
645						new DialogInterface.OnClickListener() {
646
647							@Override
648							public void onClick(DialogInterface dialog,
649									int which) {
650								xmppService.sendMessage(message,
651										presencesArray[which]);
652								chatMsg.setText("");
653							}
654						});
655				builder.create().show();
656			}
657		}
658	}
659
660	private static class ViewHolder {
661
662		protected Button download_button;
663		protected ImageView image;
664		protected ImageView indicator;
665		protected TextView time;
666		protected TextView messageBody;
667		protected ImageView contact_picture;
668
669	}
670
671	private class BitmapCache {
672		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
673		private Bitmap error = null;
674
675		public Bitmap get(String name, Contact contact, Context context) {
676			if (bitmaps.containsKey(name)) {
677				return bitmaps.get(name);
678			} else {
679				Bitmap bm;
680				if (contact != null){
681					bm = UIHelper.getContactPicture(contact, 48, context, false);
682				} else {
683					bm = UIHelper.getContactPicture(name, 48, context, false);
684				}
685				bitmaps.put(name, bm);
686				return bm;
687			}
688		}
689
690		public Bitmap getError() {
691			if (error == null) {
692				error = UIHelper.getErrorPicture(200);
693			}
694			return error;
695		}
696	}
697
698	class DecryptMessage extends AsyncTask<Message, Void, Boolean> {
699
700		@Override
701		protected Boolean doInBackground(Message... params) {
702			final ConversationActivity activity = (ConversationActivity) getActivity();
703			askForPassphraseIntent = null;
704			for (int i = 0; i < params.length; ++i) {
705				if (params[i].getEncryption() == Message.ENCRYPTION_PGP) {
706					String body = params[i].getBody();
707					String decrypted = null;
708					if (activity == null) {
709						return false;
710					} else if (!activity.xmppConnectionServiceBound) {
711						return false;
712					}
713					try {
714						decrypted = activity.xmppConnectionService
715								.getPgpEngine().decrypt(conversation.getAccount(),body);
716					} catch (UserInputRequiredException e) {
717						askForPassphraseIntent = e.getPendingIntent()
718								.getIntentSender();
719						activity.runOnUiThread(new Runnable() {
720
721							@Override
722							public void run() {
723								pgpInfo.setVisibility(View.VISIBLE);
724							}
725						});
726
727						return false;
728
729					} catch (OpenPgpException e) {
730						Log.d("gultsch", "error decrypting pgp");
731					}
732					if (decrypted != null) {
733						params[i].setBody(decrypted);
734						params[i].setEncryption(Message.ENCRYPTION_DECRYPTED);
735						activity.xmppConnectionService.updateMessage(params[i]);
736					}
737					if (activity != null) {
738						activity.runOnUiThread(new Runnable() {
739
740							@Override
741							public void run() {
742								messageListAdapter.notifyDataSetChanged();
743							}
744						});
745					}
746				}
747				if (activity != null) {
748					activity.runOnUiThread(new Runnable() {
749
750						@Override
751						public void run() {
752							activity.updateConversationList();
753						}
754					});
755				}
756			}
757			return true;
758		}
759
760	}
761
762	public void setText(String text) {
763		this.pastedText = text;
764	}
765}