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