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