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						viewHolder.messageBody.setVisibility(View.GONE);
301						viewHolder.image.setVisibility(View.VISIBLE);
302						String[] params = item.getBody().split(",");
303			        	if (params.length==3) {
304			        		int target = (int) (metrics.density * 288);
305			        		int w = Integer.parseInt(params[1]);
306			        		int h = Integer.parseInt(params[2]);
307			        		int scalledW;
308			    			int scalledH;
309			    			if (w <= h) {
310			    				scalledW = (int) (w / ((double) h / target));
311			    				scalledH = target;
312			    			} else {
313			    				scalledW = target;
314			    				scalledH = (int) (h / ((double) w / target));
315			    			}
316			        		viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(scalledW, scalledH));
317			        	} else {
318			        		Log.d("xmppService","message body has less than 3 params");
319			        	}
320						activity.loadBitmap(item, viewHolder.image);
321						viewHolder.image.setOnClickListener(new OnClickListener() {
322							
323							@Override
324							public void onClick(View v) {
325								Uri uri = Uri.parse("content://eu.siacs.conversations.images/"+item.getConversationUuid()+"/"+item.getUuid());
326								Log.d("xmppService","staring intent with uri:"+uri.toString());
327								Intent intent = new Intent(Intent.ACTION_VIEW);
328							    intent.setDataAndType(uri, "image/*");
329							    startActivity(intent);
330							}
331						});
332					}
333				} else {
334					viewHolder.image.setVisibility(View.GONE);
335					viewHolder.messageBody.setVisibility(View.VISIBLE);
336					String body = item.getBody();
337					if (body != null) {
338						if (item.getEncryption() == Message.ENCRYPTION_PGP) {
339							viewHolder.messageBody
340									.setText(getString(R.string.encrypted_message));
341							viewHolder.messageBody.setTextColor(0xff33B5E5);
342							viewHolder.messageBody.setTypeface(null,
343									Typeface.ITALIC);
344						} else if ((item.getEncryption() == Message.ENCRYPTION_OTR)||(item.getEncryption() == Message.ENCRYPTION_DECRYPTED)) {
345							viewHolder.messageBody.setText(body.trim());
346							viewHolder.messageBody.setTextColor(0xff333333);
347							viewHolder.messageBody.setTypeface(null,
348									Typeface.NORMAL);
349						} else {
350							viewHolder.messageBody.setText(body.trim());
351							viewHolder.messageBody.setTextColor(0xff333333);
352							viewHolder.messageBody.setTypeface(null,
353									Typeface.NORMAL);
354						}
355					}
356				}
357				switch (item.getStatus()) {
358				case Message.STATUS_UNSEND:
359					viewHolder.time.setTypeface(null, Typeface.ITALIC);
360					viewHolder.time.setTextColor(0xFF8e8e8e);
361					viewHolder.time.setText("sending\u2026");
362					break;
363				case Message.STATUS_OFFERED:
364					viewHolder.time.setTypeface(null, Typeface.ITALIC);
365					viewHolder.time.setTextColor(0xFF8e8e8e);
366					viewHolder.time.setText("offering\u2026");
367					break;
368				case Message.STATUS_SEND_FAILED:
369					viewHolder.time.setText(getString(R.string.send_failed) + " \u00B7 " + UIHelper.readableTimeDifference(item
370							.getTimeSent()));
371					viewHolder.time.setTextColor(0xFFe92727);
372					viewHolder.time.setTypeface(null,Typeface.NORMAL);
373					break;
374				case Message.STATUS_SEND_REJECTED:
375					viewHolder.time.setText(getString(R.string.send_rejected));
376					viewHolder.time.setTextColor(0xFFe92727);
377					viewHolder.time.setTypeface(null,Typeface.NORMAL);
378					break;
379				default:
380					viewHolder.time.setTypeface(null, Typeface.NORMAL);
381					viewHolder.time.setTextColor(0xFF8e8e8e);
382					if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
383						viewHolder.time.setText(UIHelper
384								.readableTimeDifference(item.getTimeSent()));
385					} else {
386						viewHolder.time.setText(item.getCounterpart()
387								+ " \u00B7 "
388								+ UIHelper.readableTimeDifference(item
389										.getTimeSent()));
390					}
391					break;
392				}
393				return view;
394			}
395		};
396		messagesView.setAdapter(messageListAdapter);
397
398		return view;
399	}
400
401	protected Bitmap findSelfPicture() {
402		SharedPreferences sharedPref = PreferenceManager
403				.getDefaultSharedPreferences(getActivity()
404						.getApplicationContext());
405		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
406				"show_phone_selfcontact_picture", true);
407
408		return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
409				showPhoneSelfContactPicture, getActivity());
410	}
411
412	@Override
413	public void onStart() {
414		super.onStart();
415		this.activity = (ConversationActivity) getActivity();
416		SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
417		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
418		if (activity.xmppConnectionServiceBound) {
419			this.onBackendConnected();
420		}
421	}
422
423	public void onBackendConnected() {
424		this.conversation = activity.getSelectedConversation();
425		if (this.conversation == null) {
426			return;
427		}
428		this.selfBitmap = findSelfPicture();
429		updateMessages();
430		// rendering complete. now go tell activity to close pane
431		if (activity.getSlidingPaneLayout().isSlideable()) {
432			if (!activity.shouldPaneBeOpen()) {
433				activity.getSlidingPaneLayout().closePane();
434				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
435				activity.getActionBar().setTitle(conversation.getName(useSubject));
436				activity.invalidateOptionsMenu();
437
438			}
439		}
440		if (queuedPqpMessage != null) {
441			this.conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
442			Message message = new Message(conversation, queuedPqpMessage,
443					Message.ENCRYPTION_PGP);
444			sendPgpMessage(message);
445		}
446		if (conversation.getMode() == Conversation.MODE_MULTI) {
447			activity.xmppConnectionService
448					.setOnRenameListener(new OnRenameListener() {
449
450						@Override
451						public void onRename(final boolean success) {
452							activity.xmppConnectionService.updateConversation(conversation);
453							getActivity().runOnUiThread(new Runnable() {
454
455								@Override
456								public void run() {
457									if (success) {
458										Toast.makeText(
459												getActivity(),
460												getString(R.string.your_nick_has_been_changed),
461												Toast.LENGTH_SHORT).show();
462									} else {
463										Toast.makeText(getActivity(),
464												getString(R.string.nick_in_use),
465												Toast.LENGTH_SHORT).show();
466									}
467								}
468							});
469						}
470					});
471		}
472	}
473
474	public void updateMessages() {
475		ConversationActivity activity = (ConversationActivity) getActivity();
476		if (this.conversation != null) {
477			List<Message> encryptedMessages = new LinkedList<Message>();
478			for (Message message : this.conversation.getMessages()) {
479				if (message.getEncryption() == Message.ENCRYPTION_PGP) {
480					encryptedMessages.add(message);
481				}
482			}
483			if (encryptedMessages.size() > 0) {
484				DecryptMessage task = new DecryptMessage();
485				Message[] msgs = new Message[encryptedMessages.size()];
486				task.execute(encryptedMessages.toArray(msgs));
487			}
488			this.messageList.clear();
489			this.messageList.addAll(this.conversation.getMessages());
490			this.messageListAdapter.notifyDataSetChanged();
491			if (conversation.getMode() == Conversation.MODE_SINGLE) {
492				if (messageList.size() >= 1) {
493					int latestEncryption = this.conversation.getLatestMessage()
494							.getEncryption();
495					if (latestEncryption == Message.ENCRYPTION_DECRYPTED) {
496						conversation.nextMessageEncryption = Message.ENCRYPTION_PGP;
497					} else {
498						conversation.nextMessageEncryption = latestEncryption;
499					}
500					makeFingerprintWarning(latestEncryption);
501				}
502			} else {
503				if (conversation.getMucOptions().getError() != 0) {
504					mucError.setVisibility(View.VISIBLE);
505					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
506						mucErrorText.setText(getString(R.string.nick_in_use));
507					}
508				} else {
509					mucError.setVisibility(View.GONE);
510				}
511			}
512			getActivity().invalidateOptionsMenu();
513			updateChatMsgHint();
514			int size = this.messageList.size();
515			if (size >= 1)
516				messagesView.setSelection(size - 1);
517			if (!activity.shouldPaneBeOpen()) {
518				conversation.markRead();
519				// TODO update notifications
520				UIHelper.updateNotification(getActivity(),
521						activity.getConversationList(), null, false);
522				activity.updateConversationList();
523			}
524		}
525	}
526
527	protected void makeFingerprintWarning(int latestEncryption) {
528		final LinearLayout fingerprintWarning = (LinearLayout) getView()
529				.findViewById(R.id.new_fingerprint);
530		if (conversation.getContact() != null) {
531			Set<String> knownFingerprints = conversation.getContact()
532					.getOtrFingerprints();
533			if ((latestEncryption == Message.ENCRYPTION_OTR)
534					&& (conversation.hasValidOtrSession()
535							&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
536								.contains(conversation.getOtrFingerprint())))) {
537				fingerprintWarning.setVisibility(View.VISIBLE);
538				TextView fingerprint = (TextView) getView().findViewById(
539						R.id.otr_fingerprint);
540				fingerprint.setText(conversation.getOtrFingerprint());
541				fingerprintWarning.setOnClickListener(new OnClickListener() {
542
543					@Override
544					public void onClick(View v) {
545						AlertDialog dialog = UIHelper
546								.getVerifyFingerprintDialog(
547										(ConversationActivity) getActivity(),
548										conversation, fingerprintWarning);
549						dialog.show();
550					}
551				});
552			} else {
553				fingerprintWarning.setVisibility(View.GONE);
554			}
555		} else {
556			fingerprintWarning.setVisibility(View.GONE);
557		}
558	}
559
560	protected void sendPlainTextMessage(Message message) {
561		ConversationActivity activity = (ConversationActivity) getActivity();
562		activity.xmppConnectionService.sendMessage(message, null);
563		chatMsg.setText("");
564	}
565
566	protected void sendPgpMessage(final Message message) {
567		ConversationActivity activity = (ConversationActivity) getActivity();
568		final XmppConnectionService xmppService = activity.xmppConnectionService;
569		Contact contact = message.getConversation().getContact();
570		Account account = message.getConversation().getAccount();
571		if (activity.hasPgp()) {
572			if (contact.getPgpKeyId() != 0) {
573				try {
574					message.setEncryptedBody(xmppService.getPgpEngine().encrypt(account, contact.getPgpKeyId(), message.getBody()));
575					xmppService.sendMessage(message, null);
576					chatMsg.setText("");
577				} catch (UserInputRequiredException e) {
578					try {
579						getActivity().startIntentSenderForResult(e.getPendingIntent().getIntentSender(),
580								ConversationActivity.REQUEST_SEND_MESSAGE, null, 0,
581								0, 0);
582					} catch (SendIntentException e1) {
583						Log.d("xmppService","failed to start intent to send message");
584					}
585				} catch (OpenPgpException e) {
586					Log.d("xmppService","error encrypting with pgp: "+e.getOpenPgpError().getMessage());
587				}
588			} else {
589				AlertDialog.Builder builder = new AlertDialog.Builder(
590						getActivity());
591				builder.setTitle("No openPGP key found");
592				builder.setIconAttribute(android.R.attr.alertDialogIcon);
593				builder.setMessage("There is no openPGP key associated with this contact");
594				builder.setNegativeButton("Cancel", null);
595				builder.setPositiveButton("Send plain text",
596						new DialogInterface.OnClickListener() {
597
598							@Override
599							public void onClick(DialogInterface dialog,
600									int which) {
601								conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
602								message.setEncryption(Message.ENCRYPTION_NONE);
603								xmppService.sendMessage(message, null);
604								chatMsg.setText("");
605							}
606						});
607				builder.create().show();
608			}
609		}
610	}
611
612	protected void sendOtrMessage(final Message message) {
613		ConversationActivity activity = (ConversationActivity) getActivity();
614		final XmppConnectionService xmppService = activity.xmppConnectionService;
615		if (conversation.hasValidOtrSession()) {
616			activity.xmppConnectionService.sendMessage(message, null);
617			chatMsg.setText("");
618		} else {
619			Hashtable<String, Integer> presences;
620			if (conversation.getContact() != null) {
621				presences = conversation.getContact().getPresences();
622			} else {
623				presences = null;
624			}
625			if ((presences == null) || (presences.size() == 0)) {
626				AlertDialog.Builder builder = new AlertDialog.Builder(
627						getActivity());
628				builder.setTitle("Contact is offline");
629				builder.setIconAttribute(android.R.attr.alertDialogIcon);
630				builder.setMessage("Sending OTR encrypted messages to an offline contact is impossible.");
631				builder.setPositiveButton("Send plain text",
632						new DialogInterface.OnClickListener() {
633
634							@Override
635							public void onClick(DialogInterface dialog,
636									int which) {
637								conversation.nextMessageEncryption = Message.ENCRYPTION_NONE;
638								message.setEncryption(Message.ENCRYPTION_NONE);
639								xmppService.sendMessage(message, null);
640								chatMsg.setText("");
641							}
642						});
643				builder.setNegativeButton("Cancel", null);
644				builder.create().show();
645			} else if (presences.size() == 1) {
646				xmppService.sendMessage(message, (String) presences.keySet()
647						.toArray()[0]);
648				chatMsg.setText("");
649			} else {
650				AlertDialog.Builder builder = new AlertDialog.Builder(
651						getActivity());
652				builder.setTitle("Choose Presence");
653				final String[] presencesArray = new String[presences.size()];
654				presences.keySet().toArray(presencesArray);
655				builder.setItems(presencesArray,
656						new DialogInterface.OnClickListener() {
657
658							@Override
659							public void onClick(DialogInterface dialog,
660									int which) {
661								xmppService.sendMessage(message,
662										presencesArray[which]);
663								chatMsg.setText("");
664							}
665						});
666				builder.create().show();
667			}
668		}
669	}
670
671	private static class ViewHolder {
672
673		protected Button download_button;
674		protected ImageView image;
675		protected ImageView indicator;
676		protected TextView time;
677		protected TextView messageBody;
678		protected ImageView contact_picture;
679
680	}
681
682	private class BitmapCache {
683		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
684		private Bitmap error = null;
685
686		public Bitmap get(String name, Contact contact, Context context) {
687			if (bitmaps.containsKey(name)) {
688				return bitmaps.get(name);
689			} else {
690				Bitmap bm;
691				if (contact != null){
692					bm = UIHelper.getContactPicture(contact, 48, context, false);
693				} else {
694					bm = UIHelper.getContactPicture(name, 48, context, false);
695				}
696				bitmaps.put(name, bm);
697				return bm;
698			}
699		}
700	}
701
702	class DecryptMessage extends AsyncTask<Message, Void, Boolean> {
703
704		@Override
705		protected Boolean doInBackground(Message... params) {
706			final ConversationActivity activity = (ConversationActivity) getActivity();
707			askForPassphraseIntent = null;
708			for (int i = 0; i < params.length; ++i) {
709				if (params[i].getEncryption() == Message.ENCRYPTION_PGP) {
710					String body = params[i].getBody();
711					String decrypted = null;
712					if (activity == null) {
713						return false;
714					} else if (!activity.xmppConnectionServiceBound) {
715						return false;
716					}
717					try {
718						decrypted = activity.xmppConnectionService
719								.getPgpEngine().decrypt(conversation.getAccount(),body);
720					} catch (UserInputRequiredException e) {
721						askForPassphraseIntent = e.getPendingIntent()
722								.getIntentSender();
723						activity.runOnUiThread(new Runnable() {
724
725							@Override
726							public void run() {
727								pgpInfo.setVisibility(View.VISIBLE);
728							}
729						});
730
731						return false;
732
733					} catch (OpenPgpException e) {
734						Log.d("gultsch", "error decrypting pgp");
735					}
736					if (decrypted != null) {
737						params[i].setBody(decrypted);
738						params[i].setEncryption(Message.ENCRYPTION_DECRYPTED);
739						activity.xmppConnectionService.updateMessage(params[i]);
740					}
741					if (activity != null) {
742						activity.runOnUiThread(new Runnable() {
743
744							@Override
745							public void run() {
746								messageListAdapter.notifyDataSetChanged();
747							}
748						});
749					}
750				}
751				if (activity != null) {
752					activity.runOnUiThread(new Runnable() {
753
754						@Override
755						public void run() {
756							activity.updateConversationList();
757						}
758					});
759				}
760			}
761			return true;
762		}
763
764	}
765
766	public void setText(String text) {
767		this.pastedText = text;
768	}
769}