ConversationFragment.java

  1package eu.siacs.conversations.ui;
  2
  3import java.util.ArrayList;
  4import java.util.HashMap;
  5import java.util.List;
  6import java.util.Set;
  7
  8import net.java.otr4j.session.SessionStatus;
  9import eu.siacs.conversations.R;
 10import eu.siacs.conversations.crypto.PgpEngine;
 11import eu.siacs.conversations.entities.Contact;
 12import eu.siacs.conversations.entities.Conversation;
 13import eu.siacs.conversations.entities.Message;
 14import eu.siacs.conversations.entities.MucOptions;
 15import eu.siacs.conversations.entities.MucOptions.OnRenameListener;
 16import eu.siacs.conversations.services.ImageProvider;
 17import eu.siacs.conversations.services.XmppConnectionService;
 18import eu.siacs.conversations.utils.UIHelper;
 19import eu.siacs.conversations.xmpp.jingle.JingleConnection;
 20import android.app.AlertDialog;
 21import android.app.Fragment;
 22import android.app.PendingIntent;
 23import android.content.Context;
 24import android.content.DialogInterface;
 25import android.content.Intent;
 26import android.content.IntentSender;
 27import android.content.SharedPreferences;
 28import android.content.IntentSender.SendIntentException;
 29import android.graphics.Bitmap;
 30import android.graphics.Typeface;
 31import android.os.Bundle;
 32import android.preference.PreferenceManager;
 33import android.text.Editable;
 34import android.text.Selection;
 35import android.util.DisplayMetrics;
 36import android.view.Gravity;
 37import android.view.LayoutInflater;
 38import android.view.View;
 39import android.view.View.OnClickListener;
 40import android.view.View.OnLongClickListener;
 41import android.view.ViewGroup;
 42import android.widget.AbsListView.OnScrollListener;
 43import android.widget.AbsListView;
 44import android.widget.ArrayAdapter;
 45import android.widget.Button;
 46import android.widget.EditText;
 47import android.widget.LinearLayout;
 48import android.widget.ListView;
 49import android.widget.ImageButton;
 50import android.widget.ImageView;
 51import android.widget.TextView;
 52import android.widget.Toast;
 53
 54public class ConversationFragment extends Fragment {
 55
 56	protected Conversation conversation;
 57	protected ListView messagesView;
 58	protected LayoutInflater inflater;
 59	protected List<Message> messageList = new ArrayList<Message>();
 60	protected ArrayAdapter<Message> messageListAdapter;
 61	protected Contact contact;
 62	protected BitmapCache mBitmapCache = new BitmapCache();
 63
 64	protected String queuedPqpMessage = null;
 65
 66	private EditText chatMsg;
 67	private String pastedText = null;
 68
 69	protected Bitmap selfBitmap;
 70
 71	private boolean useSubject = true;
 72	private boolean messagesLoaded = false;
 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.getNextEncryption());
 84			if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
 85				sendOtrMessage(message);
 86			} else if (conversation.getNextEncryption() == 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 (activity.hasPgp() && askForPassphraseIntent != null) {
 98				try {
 99					getActivity().startIntentSenderForResult(
100							askForPassphraseIntent,
101							ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
102							0, 0);
103				} catch (SendIntentException e) {
104					//
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
124	private OnScrollListener mOnScrollListener = new OnScrollListener() {
125
126		@Override
127		public void onScrollStateChanged(AbsListView view, int scrollState) {
128			// TODO Auto-generated method stub
129
130		}
131
132		@Override
133		public void onScroll(AbsListView view, int firstVisibleItem,
134				int visibleItemCount, int totalItemCount) {
135			if (firstVisibleItem == 0 && messagesLoaded) {
136				long timestamp = messageList.get(0).getTimeSent();
137				messagesLoaded = false;
138				List<Message> messages = activity.xmppConnectionService
139						.getMoreMessages(conversation, timestamp);
140				messageList.addAll(0, messages);
141				messageListAdapter.notifyDataSetChanged();
142				if (messages.size() != 0) {
143					messagesLoaded = true;
144				}
145				messagesView.setSelectionFromTop(messages.size() + 1, 0);
146			}
147		}
148	};
149
150	private ConversationActivity activity;
151
152	public void hidePgpPassphraseBox() {
153		pgpInfo.setVisibility(View.GONE);
154	}
155
156	public void updateChatMsgHint() {
157		switch (conversation.getNextEncryption()) {
158		case Message.ENCRYPTION_NONE:
159			chatMsg.setHint(getString(R.string.send_plain_text_message));
160			break;
161		case Message.ENCRYPTION_OTR:
162			chatMsg.setHint(getString(R.string.send_otr_message));
163			break;
164		case Message.ENCRYPTION_PGP:
165			chatMsg.setHint(getString(R.string.send_pgp_message));
166			break;
167		default:
168			break;
169		}
170	}
171
172	@Override
173	public View onCreateView(final LayoutInflater inflater,
174			ViewGroup container, Bundle savedInstanceState) {
175
176		final DisplayMetrics metrics = getResources().getDisplayMetrics();
177
178		this.inflater = inflater;
179
180		final View view = inflater.inflate(R.layout.fragment_conversation,
181				container, false);
182		chatMsg = (EditText) view.findViewById(R.id.textinput);
183
184		ImageButton sendButton = (ImageButton) view
185				.findViewById(R.id.textSendButton);
186		sendButton.setOnClickListener(this.sendMsgListener);
187
188		pgpInfo = (LinearLayout) view.findViewById(R.id.pgp_keyentry);
189		pgpInfo.setOnClickListener(clickToDecryptListener);
190		mucError = (LinearLayout) view.findViewById(R.id.muc_error);
191		mucError.setOnClickListener(clickToMuc);
192		mucErrorText = (TextView) view.findViewById(R.id.muc_error_msg);
193
194		messagesView = (ListView) view.findViewById(R.id.messages_view);
195		messagesView.setOnScrollListener(mOnScrollListener);
196		messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
197
198		messageListAdapter = new ArrayAdapter<Message>(this.getActivity()
199				.getApplicationContext(), R.layout.message_sent,
200				this.messageList) {
201
202			private static final int SENT = 0;
203			private static final int RECIEVED = 1;
204			private static final int STATUS = 2;
205
206			@Override
207			public int getViewTypeCount() {
208				return 3;
209			}
210
211			@Override
212			public int getItemViewType(int position) {
213				if (getItem(position).getType() == Message.TYPE_STATUS) {
214					return STATUS;
215				} else if (getItem(position).getStatus() <= Message.STATUS_RECIEVED) {
216					return RECIEVED;
217				} else {
218					return SENT;
219				}
220			}
221
222			private void displayStatus(ViewHolder viewHolder, Message message) {
223				String filesize = null;
224				String info = null;
225				boolean error = false;
226				boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
227						&& message.getStatus() <= Message.STATUS_RECIEVED;
228				if (message.getType() == Message.TYPE_IMAGE) {
229					String[] fileParams = message.getBody().split(",");
230					try {
231						long size = Long.parseLong(fileParams[0]);
232						filesize = size / 1024 + " KB";
233					} catch (NumberFormatException e) {
234						filesize = "0 KB";
235					}
236				}
237				switch (message.getStatus()) {
238				case Message.STATUS_WAITING:
239					info = getString(R.string.waiting);
240					break;
241				case Message.STATUS_UNSEND:
242					info = getString(R.string.sending);
243					break;
244				case Message.STATUS_OFFERED:
245					info = getString(R.string.offering);
246					break;
247				case Message.STATUS_SEND_FAILED:
248					info = getString(R.string.send_failed);
249					error = true;
250					break;
251				case Message.STATUS_SEND_REJECTED:
252					info = getString(R.string.send_rejected);
253					error = true;
254					break;
255				default:
256					if (multiReceived) {
257						info = message.getCounterpart();
258					}
259					break;
260				}
261				if (error) {
262					viewHolder.time.setTextColor(0xFFe92727);
263				} else {
264					viewHolder.time.setTextColor(0xFF8e8e8e);
265				}
266				if (message.getEncryption() == Message.ENCRYPTION_NONE) {
267					viewHolder.indicator.setVisibility(View.GONE);
268				} else {
269					viewHolder.indicator.setVisibility(View.VISIBLE);
270				}
271
272				String formatedTime = UIHelper.readableTimeDifference(
273						getContext(), message.getTimeSent());
274				if (message.getStatus() <= Message.STATUS_RECIEVED) {
275					if ((filesize != null) && (info != null)) {
276						viewHolder.time.setText(filesize + " \u00B7 " + info);
277					} else if ((filesize == null) && (info != null)) {
278						viewHolder.time.setText(formatedTime + " \u00B7 "
279								+ info);
280					} else if ((filesize != null) && (info == null)) {
281						viewHolder.time.setText(formatedTime + " \u00B7 "
282								+ filesize);
283					} else {
284						viewHolder.time.setText(formatedTime);
285					}
286				} else {
287					if ((filesize != null) && (info != null)) {
288						viewHolder.time.setText(filesize + " \u00B7 " + info);
289					} else if ((filesize == null) && (info != null)) {
290						if (error) {
291							viewHolder.time.setText(info + " \u00B7 "
292									+ formatedTime);
293						} else {
294							viewHolder.time.setText(info);
295						}
296					} else if ((filesize != null) && (info == null)) {
297						viewHolder.time.setText(filesize + " \u00B7 "
298								+ formatedTime);
299					} else {
300						viewHolder.time.setText(formatedTime);
301					}
302				}
303			}
304
305			private void displayInfoMessage(ViewHolder viewHolder, int r) {
306				if (viewHolder.download_button != null) {
307					viewHolder.download_button.setVisibility(View.GONE);
308				}
309				viewHolder.image.setVisibility(View.GONE);
310				viewHolder.messageBody.setVisibility(View.VISIBLE);
311				viewHolder.messageBody.setText(getString(r));
312				viewHolder.messageBody.setTextColor(0xff33B5E5);
313				viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
314				viewHolder.messageBody.setTextIsSelectable(false);
315			}
316
317			private void displayDecryptionFailed(ViewHolder viewHolder) {
318				if (viewHolder.download_button != null) {
319					viewHolder.download_button.setVisibility(View.GONE);
320				}
321				viewHolder.image.setVisibility(View.GONE);
322				viewHolder.messageBody.setVisibility(View.VISIBLE);
323				viewHolder.messageBody
324						.setText(getString(R.string.decryption_failed));
325				viewHolder.messageBody.setTextColor(0xFFe92727);
326				viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
327				viewHolder.messageBody.setTextIsSelectable(false);
328			}
329
330			private void displayTextMessage(ViewHolder viewHolder, String text) {
331				if (viewHolder.download_button != null) {
332					viewHolder.download_button.setVisibility(View.GONE);
333				}
334				viewHolder.image.setVisibility(View.GONE);
335				viewHolder.messageBody.setVisibility(View.VISIBLE);
336				if (text != null) {
337					viewHolder.messageBody.setText(text.trim());
338				} else {
339					viewHolder.messageBody.setText("");
340				}
341				viewHolder.messageBody.setTextColor(0xff333333);
342				viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
343				viewHolder.messageBody.setTextIsSelectable(true);
344			}
345
346			private void displayImageMessage(ViewHolder viewHolder,
347					final Message message) {
348				if (viewHolder.download_button != null) {
349					viewHolder.download_button.setVisibility(View.GONE);
350				}
351				viewHolder.messageBody.setVisibility(View.GONE);
352				viewHolder.image.setVisibility(View.VISIBLE);
353				String[] fileParams = message.getBody().split(",");
354				if (fileParams.length == 3) {
355					double target = metrics.density * 288;
356					int w = Integer.parseInt(fileParams[1]);
357					int h = Integer.parseInt(fileParams[2]);
358					int scalledW;
359					int scalledH;
360					if (w <= h) {
361						scalledW = (int) (w / ((double) h / target));
362						scalledH = (int) target;
363					} else {
364						scalledW = (int) target;
365						scalledH = (int) (h / ((double) w / target));
366					}
367					viewHolder.image
368							.setLayoutParams(new LinearLayout.LayoutParams(
369									scalledW, scalledH));
370				}
371				activity.loadBitmap(message, viewHolder.image);
372				viewHolder.image.setOnClickListener(new OnClickListener() {
373
374					@Override
375					public void onClick(View v) {
376						Intent intent = new Intent(Intent.ACTION_VIEW);
377						intent.setDataAndType(
378								ImageProvider.getContentUri(message), "image/*");
379						startActivity(intent);
380					}
381				});
382				viewHolder.image
383						.setOnLongClickListener(new OnLongClickListener() {
384
385							@Override
386							public boolean onLongClick(View v) {
387								Intent shareIntent = new Intent();
388								shareIntent.setAction(Intent.ACTION_SEND);
389								shareIntent.putExtra(Intent.EXTRA_STREAM,
390										ImageProvider.getContentUri(message));
391								shareIntent.setType("image/webp");
392								startActivity(Intent.createChooser(shareIntent,
393										getText(R.string.share_with)));
394								return true;
395							}
396						});
397			}
398
399			@Override
400			public View getView(int position, View view, ViewGroup parent) {
401				final Message item = getItem(position);
402				int type = getItemViewType(position);
403				ViewHolder viewHolder;
404				if (view == null) {
405					viewHolder = new ViewHolder();
406					switch (type) {
407					case SENT:
408						view = (View) inflater.inflate(R.layout.message_sent,
409								null);
410						viewHolder.message_box = (LinearLayout) view
411								.findViewById(R.id.message_box);
412						viewHolder.contact_picture = (ImageView) view
413								.findViewById(R.id.message_photo);
414						viewHolder.contact_picture.setImageBitmap(selfBitmap);
415						viewHolder.indicator = (ImageView) view
416								.findViewById(R.id.security_indicator);
417						viewHolder.image = (ImageView) view
418								.findViewById(R.id.message_image);
419						viewHolder.messageBody = (TextView) view
420								.findViewById(R.id.message_body);
421						viewHolder.time = (TextView) view
422								.findViewById(R.id.message_time);
423						view.setTag(viewHolder);
424						break;
425					case RECIEVED:
426						view = (View) inflater.inflate(
427								R.layout.message_recieved, null);
428						viewHolder.message_box = (LinearLayout) view
429								.findViewById(R.id.message_box);
430						viewHolder.contact_picture = (ImageView) view
431								.findViewById(R.id.message_photo);
432
433						viewHolder.download_button = (Button) view
434								.findViewById(R.id.download_button);
435
436						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
437
438							viewHolder.contact_picture
439									.setImageBitmap(mBitmapCache.get(
440											item.getConversation().getName(
441													useSubject), item
442													.getConversation()
443													.getContact(),
444											getActivity()
445													.getApplicationContext()));
446
447						}
448						viewHolder.indicator = (ImageView) view
449								.findViewById(R.id.security_indicator);
450						viewHolder.image = (ImageView) view
451								.findViewById(R.id.message_image);
452						viewHolder.messageBody = (TextView) view
453								.findViewById(R.id.message_body);
454						viewHolder.time = (TextView) view
455								.findViewById(R.id.message_time);
456						view.setTag(viewHolder);
457						break;
458					case STATUS:
459						view = (View) inflater.inflate(R.layout.message_status,
460								null);
461						viewHolder.contact_picture = (ImageView) view
462								.findViewById(R.id.message_photo);
463						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
464
465							viewHolder.contact_picture
466									.setImageBitmap(mBitmapCache.get(
467											item.getConversation().getName(
468													useSubject), item
469													.getConversation()
470													.getContact(),
471											getActivity()
472													.getApplicationContext()));
473							viewHolder.contact_picture.setAlpha(128);
474
475						}
476						break;
477					default:
478						viewHolder = null;
479						break;
480					}
481				} else {
482					viewHolder = (ViewHolder) view.getTag();
483				}
484
485				if (type == STATUS) {
486					return view;
487				}
488
489				if (type == RECIEVED) {
490					if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
491						viewHolder.contact_picture.setImageBitmap(mBitmapCache
492								.get(item.getCounterpart(), null, getActivity()
493										.getApplicationContext()));
494						viewHolder.contact_picture
495								.setOnClickListener(new OnClickListener() {
496
497									@Override
498									public void onClick(View v) {
499										highlightInConference(item
500												.getCounterpart());
501									}
502								});
503					}
504				}
505
506				if (item.getType() == Message.TYPE_IMAGE) {
507					if (item.getStatus() == Message.STATUS_RECIEVING) {
508						displayInfoMessage(viewHolder, R.string.receiving_image);
509					} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
510						viewHolder.image.setVisibility(View.GONE);
511						viewHolder.messageBody.setVisibility(View.GONE);
512						viewHolder.download_button.setVisibility(View.VISIBLE);
513						viewHolder.download_button
514								.setOnClickListener(new OnClickListener() {
515
516									@Override
517									public void onClick(View v) {
518										JingleConnection connection = item
519												.getJingleConnection();
520										if (connection != null) {
521											connection.accept();
522										}
523									}
524								});
525					} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
526							|| (item.getEncryption() == Message.ENCRYPTION_NONE)
527							|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
528						displayImageMessage(viewHolder, item);
529					} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
530						displayInfoMessage(viewHolder,
531								R.string.encrypted_message);
532					} else {
533						displayDecryptionFailed(viewHolder);
534					}
535				} else {
536					if (item.getEncryption() == Message.ENCRYPTION_PGP) {
537						if (activity.hasPgp()) {
538							displayInfoMessage(viewHolder,
539									R.string.encrypted_message);
540						} else {
541							displayInfoMessage(viewHolder,
542									R.string.install_openkeychain);
543							viewHolder.message_box
544									.setOnClickListener(new OnClickListener() {
545
546										@Override
547										public void onClick(View v) {
548											activity.showInstallPgpDialog();
549										}
550									});
551						}
552					} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
553						displayDecryptionFailed(viewHolder);
554					} else {
555						displayTextMessage(viewHolder, item.getBody());
556					}
557				}
558
559				displayStatus(viewHolder, item);
560
561				return view;
562			}
563		};
564		messagesView.setAdapter(messageListAdapter);
565
566		return view;
567	}
568
569	protected void highlightInConference(String nick) {
570		String oldString = chatMsg.getText().toString().trim();
571		if (oldString.isEmpty()) {
572			chatMsg.setText(nick + ": ");
573		} else {
574			chatMsg.setText(oldString + " " + nick + " ");
575		}
576		int position = chatMsg.length();
577		Editable etext = chatMsg.getText();
578		Selection.setSelection(etext, position);
579	}
580
581	protected Bitmap findSelfPicture() {
582		SharedPreferences sharedPref = PreferenceManager
583				.getDefaultSharedPreferences(getActivity()
584						.getApplicationContext());
585		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
586				"show_phone_selfcontact_picture", true);
587
588		return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
589				showPhoneSelfContactPicture, getActivity());
590	}
591
592	@Override
593	public void onStart() {
594		super.onStart();
595		this.activity = (ConversationActivity) getActivity();
596		SharedPreferences preferences = PreferenceManager
597				.getDefaultSharedPreferences(activity);
598		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
599		if (activity.xmppConnectionServiceBound) {
600			this.onBackendConnected();
601		}
602	}
603
604	@Override
605	public void onStop() {
606		super.onStop();
607		if (this.conversation != null) {
608			this.conversation.setNextMessage(chatMsg.getText().toString());
609		}
610	}
611
612	public void onBackendConnected() {
613		this.conversation = activity.getSelectedConversation();
614		if (this.conversation == null) {
615			return;
616		}
617		String oldString = conversation.getNextMessage().trim();
618		if (this.pastedText == null) {
619			this.chatMsg.setText(oldString);
620		} else {
621
622			if (oldString.isEmpty()) {
623				chatMsg.setText(pastedText);
624			} else {
625				chatMsg.setText(oldString + " " + pastedText);
626			}
627			pastedText = null;
628		}
629		int position = chatMsg.length();
630		Editable etext = chatMsg.getText();
631		Selection.setSelection(etext, position);
632		this.selfBitmap = findSelfPicture();
633		updateMessages();
634		if (activity.getSlidingPaneLayout().isSlideable()) {
635			if (!activity.shouldPaneBeOpen()) {
636				activity.getSlidingPaneLayout().closePane();
637				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
638				activity.getActionBar().setHomeButtonEnabled(true);
639				activity.getActionBar().setTitle(
640						conversation.getName(useSubject));
641				activity.invalidateOptionsMenu();
642			}
643		}
644		if (conversation.getMode() == Conversation.MODE_MULTI) {
645			activity.xmppConnectionService
646					.setOnRenameListener(new OnRenameListener() {
647
648						@Override
649						public void onRename(final boolean success) {
650							activity.xmppConnectionService
651									.updateConversation(conversation);
652							getActivity().runOnUiThread(new Runnable() {
653
654								@Override
655								public void run() {
656									if (success) {
657										Toast.makeText(
658												getActivity(),
659												getString(R.string.your_nick_has_been_changed),
660												Toast.LENGTH_SHORT).show();
661									} else {
662										Toast.makeText(
663												getActivity(),
664												getString(R.string.nick_in_use),
665												Toast.LENGTH_SHORT).show();
666									}
667								}
668							});
669						}
670					});
671		}
672	}
673
674	private void decryptMessage(Message message) {
675		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
676		if (engine != null) {
677			engine.decrypt(message, new UiCallback<Message>() {
678
679				@Override
680				public void userInputRequried(PendingIntent pi, Message message) {
681					askForPassphraseIntent = pi.getIntentSender();
682					pgpInfo.setVisibility(View.VISIBLE);
683				}
684
685				@Override
686				public void success(Message message) {
687					activity.xmppConnectionService.databaseBackend
688							.updateMessage(message);
689					updateMessages();
690				}
691
692				@Override
693				public void error(int error, Message message) {
694					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
695					// updateMessages();
696				}
697			});
698		} else {
699			pgpInfo.setVisibility(View.GONE);
700		}
701	}
702
703	public void updateMessages() {
704		if (getView() == null) {
705			return;
706		}
707		ConversationActivity activity = (ConversationActivity) getActivity();
708		if (this.conversation != null) {
709			for (Message message : this.conversation.getMessages()) {
710				if ((message.getEncryption() == Message.ENCRYPTION_PGP)
711						&& ((message.getStatus() == Message.STATUS_RECIEVED) || (message
712								.getStatus() == Message.STATUS_SEND))) {
713					decryptMessage(message);
714					break;
715				}
716			}
717			if (this.conversation.getMessages().size() == 0) {
718				this.messageList.clear();
719				messagesLoaded = false;
720			} else {
721				for (Message message : this.conversation.getMessages()) {
722					if (!this.messageList.contains(message)) {
723						this.messageList.add(message);
724					}
725				}
726				messagesLoaded = true;
727				updateStatusMessages();
728			}
729			this.messageListAdapter.notifyDataSetChanged();
730			if (conversation.getMode() == Conversation.MODE_SINGLE) {
731				if (messageList.size() >= 1) {
732					makeFingerprintWarning(conversation.getLatestEncryption());
733				}
734			} else {
735				if (conversation.getMucOptions().getError() != 0) {
736					mucError.setVisibility(View.VISIBLE);
737					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
738						mucErrorText.setText(getString(R.string.nick_in_use));
739					}
740				} else {
741					mucError.setVisibility(View.GONE);
742				}
743			}
744			getActivity().invalidateOptionsMenu();
745			updateChatMsgHint();
746			if (!activity.shouldPaneBeOpen()) {
747				activity.xmppConnectionService.markRead(conversation);
748				// TODO update notifications
749				UIHelper.updateNotification(getActivity(),
750						activity.getConversationList(), null, false);
751				activity.updateConversationList();
752			}
753		}
754	}
755
756	private void messageSent() {
757		int size = this.messageList.size();
758		if (size >= 1) {
759			messagesView.setSelection(size - 1);
760		}
761		chatMsg.setText("");
762	}
763
764	protected void updateStatusMessages() {
765		boolean addedStatusMsg = false;
766		if (conversation.getMode() == Conversation.MODE_SINGLE) {
767			for (int i = this.messageList.size() - 1; i >= 0; --i) {
768				if (addedStatusMsg) {
769					if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
770						this.messageList.remove(i);
771						--i;
772					}
773				} else {
774					if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
775						addedStatusMsg = true;
776					} else {
777						if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
778							this.messageList.add(i + 1,
779									Message.createStatusMessage(conversation));
780							addedStatusMsg = true;
781						}
782					}
783				}
784			}
785		}
786	}
787
788	protected void makeFingerprintWarning(int latestEncryption) {
789		final LinearLayout fingerprintWarning = (LinearLayout) getView()
790				.findViewById(R.id.new_fingerprint);
791		Set<String> knownFingerprints = conversation.getContact()
792				.getOtrFingerprints();
793		if ((latestEncryption == Message.ENCRYPTION_OTR)
794				&& (conversation.hasValidOtrSession()
795						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
796							.contains(conversation.getOtrFingerprint())))) {
797			fingerprintWarning.setVisibility(View.VISIBLE);
798			TextView fingerprint = (TextView) getView().findViewById(
799					R.id.otr_fingerprint);
800			fingerprint.setText(conversation.getOtrFingerprint());
801			fingerprintWarning.setOnClickListener(new OnClickListener() {
802
803				@Override
804				public void onClick(View v) {
805					AlertDialog dialog = UIHelper.getVerifyFingerprintDialog(
806							(ConversationActivity) getActivity(), conversation,
807							fingerprintWarning);
808					dialog.show();
809				}
810			});
811		} else {
812			fingerprintWarning.setVisibility(View.GONE);
813		}
814	}
815
816	protected void sendPlainTextMessage(Message message) {
817		ConversationActivity activity = (ConversationActivity) getActivity();
818		activity.xmppConnectionService.sendMessage(message);
819		messageSent();
820	}
821
822	protected void sendPgpMessage(final Message message) {
823		final ConversationActivity activity = (ConversationActivity) getActivity();
824		final XmppConnectionService xmppService = activity.xmppConnectionService;
825		final Contact contact = message.getConversation().getContact();
826		if (activity.hasPgp()) {
827			if (conversation.getMode() == Conversation.MODE_SINGLE) {
828				if (contact.getPgpKeyId() != 0) {
829					xmppService.getPgpEngine().hasKey(contact,
830							new UiCallback<Contact>() {
831
832								@Override
833								public void userInputRequried(PendingIntent pi,
834										Contact contact) {
835									activity.runIntent(
836											pi,
837											ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
838								}
839
840								@Override
841								public void success(Contact contact) {
842									messageSent();
843									activity.encryptTextMessage(message);
844								}
845
846								@Override
847								public void error(int error, Contact contact) {
848
849								}
850							});
851
852				} else {
853					showNoPGPKeyDialog(false,
854							new DialogInterface.OnClickListener() {
855
856								@Override
857								public void onClick(DialogInterface dialog,
858										int which) {
859									conversation
860											.setNextEncryption(Message.ENCRYPTION_NONE);
861									message.setEncryption(Message.ENCRYPTION_NONE);
862									xmppService.sendMessage(message);
863									messageSent();
864								}
865							});
866				}
867			} else {
868				if (conversation.getMucOptions().pgpKeysInUse()) {
869					if (!conversation.getMucOptions().everybodyHasKeys()) {
870						Toast warning = Toast
871								.makeText(getActivity(),
872										R.string.missing_public_keys,
873										Toast.LENGTH_LONG);
874						warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
875						warning.show();
876					}
877					activity.encryptTextMessage(message);
878					messageSent();
879				} else {
880					showNoPGPKeyDialog(true,
881							new DialogInterface.OnClickListener() {
882
883								@Override
884								public void onClick(DialogInterface dialog,
885										int which) {
886									conversation
887											.setNextEncryption(Message.ENCRYPTION_NONE);
888									message.setEncryption(Message.ENCRYPTION_NONE);
889									xmppService.sendMessage(message);
890									messageSent();
891								}
892							});
893				}
894			}
895		} else {
896			activity.showInstallPgpDialog();
897		}
898	}
899
900	public void showNoPGPKeyDialog(boolean plural,
901			DialogInterface.OnClickListener listener) {
902		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
903		builder.setIconAttribute(android.R.attr.alertDialogIcon);
904		if (plural) {
905			builder.setTitle(getString(R.string.no_pgp_keys));
906			builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
907		} else {
908			builder.setTitle(getString(R.string.no_pgp_key));
909			builder.setMessage(getText(R.string.contact_has_no_pgp_key));
910		}
911		builder.setNegativeButton(getString(R.string.cancel), null);
912		builder.setPositiveButton(getString(R.string.send_unencrypted),
913				listener);
914		builder.create().show();
915	}
916
917	protected void sendOtrMessage(final Message message) {
918		final ConversationActivity activity = (ConversationActivity) getActivity();
919		final XmppConnectionService xmppService = activity.xmppConnectionService;
920		if (conversation.hasValidOtrSession()) {
921			activity.xmppConnectionService.sendMessage(message);
922			messageSent();
923		} else {
924			activity.selectPresence(message.getConversation(),
925					new OnPresenceSelected() {
926
927						@Override
928						public void onPresenceSelected() {
929							message.setPresence(conversation.getNextPresence());
930							xmppService.sendMessage(message);
931							messageSent();
932						}
933					});
934		}
935	}
936
937	private static class ViewHolder {
938
939		protected LinearLayout message_box;
940		protected Button download_button;
941		protected ImageView image;
942		protected ImageView indicator;
943		protected TextView time;
944		protected TextView messageBody;
945		protected ImageView contact_picture;
946
947	}
948
949	private class BitmapCache {
950		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
951
952		public Bitmap get(String name, Contact contact, Context context) {
953			if (bitmaps.containsKey(name)) {
954				return bitmaps.get(name);
955			} else {
956				Bitmap bm;
957				if (contact != null) {
958					bm = UIHelper
959							.getContactPicture(contact, 48, context, false);
960				} else {
961					bm = UIHelper.getContactPicture(name, 48, context, false);
962				}
963				bitmaps.put(name, bm);
964				return bm;
965			}
966		}
967	}
968
969	public void setText(String text) {
970		this.pastedText = text;
971	}
972
973	public void clearInputField() {
974		this.chatMsg.setText("");
975	}
976}