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							viewHolder.contact_picture.setOnClickListener(new OnClickListener() {
498								
499								@Override
500								public void onClick(View v) {
501									Toast.makeText(getActivity(), R.string.contact_has_read_up_to_this_point, Toast.LENGTH_SHORT).show();	
502								}
503							});
504
505						}
506						break;
507					default:
508						viewHolder = null;
509						break;
510					}
511				} else {
512					viewHolder = (ViewHolder) view.getTag();
513				}
514
515				if (type == STATUS) {
516					return view;
517				}
518
519				if (type == RECIEVED) {
520					if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
521						viewHolder.contact_picture.setImageBitmap(mBitmapCache
522								.get(item.getCounterpart(), null, getActivity()
523										.getApplicationContext()));
524						viewHolder.contact_picture
525								.setOnClickListener(new OnClickListener() {
526
527									@Override
528									public void onClick(View v) {
529										highlightInConference(item
530												.getCounterpart());
531									}
532								});
533					}
534				}
535
536				if (item.getType() == Message.TYPE_IMAGE) {
537					if (item.getStatus() == Message.STATUS_RECIEVING) {
538						displayInfoMessage(viewHolder, R.string.receiving_image);
539					} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
540						viewHolder.image.setVisibility(View.GONE);
541						viewHolder.messageBody.setVisibility(View.GONE);
542						viewHolder.download_button.setVisibility(View.VISIBLE);
543						viewHolder.download_button
544								.setOnClickListener(new OnClickListener() {
545
546									@Override
547									public void onClick(View v) {
548										JingleConnection connection = item
549												.getJingleConnection();
550										if (connection != null) {
551											connection.accept();
552										}
553									}
554								});
555					} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
556							|| (item.getEncryption() == Message.ENCRYPTION_NONE)
557							|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
558						displayImageMessage(viewHolder, item);
559					} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
560						displayInfoMessage(viewHolder,
561								R.string.encrypted_message);
562					} else {
563						displayDecryptionFailed(viewHolder);
564					}
565				} else {
566					if (item.getEncryption() == Message.ENCRYPTION_PGP) {
567						if (activity.hasPgp()) {
568							displayInfoMessage(viewHolder,
569									R.string.encrypted_message);
570						} else {
571							displayInfoMessage(viewHolder,
572									R.string.install_openkeychain);
573							viewHolder.message_box
574									.setOnClickListener(new OnClickListener() {
575
576										@Override
577										public void onClick(View v) {
578											activity.showInstallPgpDialog();
579										}
580									});
581						}
582					} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
583						displayDecryptionFailed(viewHolder);
584					} else {
585						displayTextMessage(viewHolder, item.getBody());
586					}
587				}
588
589				displayStatus(viewHolder, item);
590
591				return view;
592			}
593		};
594		messagesView.setAdapter(messageListAdapter);
595
596		return view;
597	}
598
599	protected void highlightInConference(String nick) {
600		String oldString = chatMsg.getText().toString().trim();
601		if (oldString.isEmpty()) {
602			chatMsg.setText(nick + ": ");
603		} else {
604			chatMsg.setText(oldString + " " + nick + " ");
605		}
606		int position = chatMsg.length();
607		Editable etext = chatMsg.getText();
608		Selection.setSelection(etext, position);
609	}
610
611	protected Bitmap findSelfPicture() {
612		SharedPreferences sharedPref = PreferenceManager
613				.getDefaultSharedPreferences(getActivity()
614						.getApplicationContext());
615		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
616				"show_phone_selfcontact_picture", true);
617
618		return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
619				showPhoneSelfContactPicture, getActivity());
620	}
621
622	@Override
623	public void onStart() {
624		super.onStart();
625		this.activity = (ConversationActivity) getActivity();
626		SharedPreferences preferences = PreferenceManager
627				.getDefaultSharedPreferences(activity);
628		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
629		if (activity.xmppConnectionServiceBound) {
630			this.onBackendConnected();
631		}
632	}
633
634	@Override
635	public void onStop() {
636		super.onStop();
637		if (this.conversation != null) {
638			this.conversation.setNextMessage(chatMsg.getText().toString());
639		}
640	}
641
642	public void onBackendConnected() {
643		this.conversation = activity.getSelectedConversation();
644		if (this.conversation == null) {
645			return;
646		}
647		String oldString = conversation.getNextMessage().trim();
648		if (this.pastedText == null) {
649			this.chatMsg.setText(oldString);
650		} else {
651
652			if (oldString.isEmpty()) {
653				chatMsg.setText(pastedText);
654			} else {
655				chatMsg.setText(oldString + " " + pastedText);
656			}
657			pastedText = null;
658		}
659		int position = chatMsg.length();
660		Editable etext = chatMsg.getText();
661		Selection.setSelection(etext, position);
662		this.selfBitmap = findSelfPicture();
663		updateMessages();
664		if (activity.getSlidingPaneLayout().isSlideable()) {
665			if (!activity.shouldPaneBeOpen()) {
666				activity.getSlidingPaneLayout().closePane();
667				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
668				activity.getActionBar().setHomeButtonEnabled(true);
669				activity.getActionBar().setTitle(
670						conversation.getName(useSubject));
671				activity.invalidateOptionsMenu();
672			}
673		}
674	}
675
676	private void decryptMessage(Message message) {
677		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
678		if (engine != null) {
679			engine.decrypt(message, new UiCallback<Message>() {
680
681				@Override
682				public void userInputRequried(PendingIntent pi, Message message) {
683					askForPassphraseIntent = pi.getIntentSender();
684					showSnackbar(R.string.openpgp_messages_found,
685							R.string.decrypt, clickToDecryptListener);
686				}
687
688				@Override
689				public void success(Message message) {
690					activity.xmppConnectionService.databaseBackend
691							.updateMessage(message);
692					updateMessages();
693				}
694
695				@Override
696				public void error(int error, Message message) {
697					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
698					// updateMessages();
699				}
700			});
701		}
702	}
703
704	public void updateMessages() {
705		if (getView() == null) {
706			return;
707		}
708		hideSnackbar();
709		final ConversationActivity activity = (ConversationActivity) getActivity();
710		if (this.conversation != null) {
711			final Contact contact = this.conversation.getContact();
712			if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
713				showSnackbar(R.string.contact_added_you, R.string.add_back, new OnClickListener() {
714					
715					@Override
716					public void onClick(View v) {
717						activity.xmppConnectionService.createContact(contact);
718						activity.switchToContactDetails(contact);
719					}
720				});
721			}
722			for (Message message : this.conversation.getMessages()) {
723				if ((message.getEncryption() == Message.ENCRYPTION_PGP)
724						&& ((message.getStatus() == Message.STATUS_RECIEVED) || (message
725								.getStatus() == Message.STATUS_SEND))) {
726					decryptMessage(message);
727					break;
728				}
729			}
730			if (this.conversation.getMessages().size() == 0) {
731				this.messageList.clear();
732				messagesLoaded = false;
733			} else {
734				for (Message message : this.conversation.getMessages()) {
735					if (!this.messageList.contains(message)) {
736						this.messageList.add(message);
737					}
738				}
739				messagesLoaded = true;
740				updateStatusMessages();
741			}
742			this.messageListAdapter.notifyDataSetChanged();
743			if (conversation.getMode() == Conversation.MODE_SINGLE) {
744				if (messageList.size() >= 1) {
745					makeFingerprintWarning(conversation.getLatestEncryption());
746				}
747			} else {
748				if (!conversation.getMucOptions().online()
749						&& conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
750					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
751						showSnackbar(R.string.nick_in_use, R.string.edit,
752								clickToMuc);
753					} else if (conversation.getMucOptions().getError() == MucOptions.ERROR_ROOM_NOT_FOUND) {
754						showSnackbar(R.string.conference_not_found,
755								R.string.leave, leaveMuc);
756					}
757				}
758			}
759			getActivity().invalidateOptionsMenu();
760			updateChatMsgHint();
761			if (!activity.shouldPaneBeOpen()) {
762				activity.xmppConnectionService.markRead(conversation);
763				// TODO update notifications
764				UIHelper.updateNotification(getActivity(),
765						activity.getConversationList(), null, false);
766				activity.updateConversationList();
767			}
768		}
769	}
770
771	private void messageSent() {
772		int size = this.messageList.size();
773		if (size >= 1) {
774			messagesView.setSelection(size - 1);
775		}
776		chatMsg.setText("");
777	}
778
779	protected void updateStatusMessages() {
780		boolean addedStatusMsg = false;
781		if (conversation.getMode() == Conversation.MODE_SINGLE) {
782			for (int i = this.messageList.size() - 1; i >= 0; --i) {
783				if (addedStatusMsg) {
784					if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
785						this.messageList.remove(i);
786						--i;
787					}
788				} else {
789					if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
790						addedStatusMsg = true;
791					} else {
792						if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
793							this.messageList.add(i + 1,
794									Message.createStatusMessage(conversation));
795							addedStatusMsg = true;
796						}
797					}
798				}
799			}
800		}
801	}
802
803	protected void makeFingerprintWarning(int latestEncryption) {
804		Set<String> knownFingerprints = conversation.getContact()
805				.getOtrFingerprints();
806		if ((latestEncryption == Message.ENCRYPTION_OTR)
807				&& (conversation.hasValidOtrSession()
808						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
809							.contains(conversation.getOtrFingerprint())))) {
810			showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
811					new OnClickListener() {
812
813						@Override
814						public void onClick(View v) {
815							if (conversation.getOtrFingerprint() != null) {
816								AlertDialog dialog = UIHelper
817										.getVerifyFingerprintDialog(
818												(ConversationActivity) getActivity(),
819												conversation, snackbar);
820								dialog.show();
821							}
822						}
823					});
824		}
825	}
826
827	protected void showSnackbar(int message, int action,
828			OnClickListener clickListener) {
829		snackbar.setVisibility(View.VISIBLE);
830		snackbarMessage.setText(message);
831		snackbarAction.setText(action);
832		snackbarAction.setOnClickListener(clickListener);
833	}
834
835	protected void hideSnackbar() {
836		snackbar.setVisibility(View.GONE);
837	}
838
839	protected void sendPlainTextMessage(Message message) {
840		ConversationActivity activity = (ConversationActivity) getActivity();
841		activity.xmppConnectionService.sendMessage(message);
842		messageSent();
843	}
844
845	protected void sendPgpMessage(final Message message) {
846		final ConversationActivity activity = (ConversationActivity) getActivity();
847		final XmppConnectionService xmppService = activity.xmppConnectionService;
848		final Contact contact = message.getConversation().getContact();
849		if (activity.hasPgp()) {
850			if (conversation.getMode() == Conversation.MODE_SINGLE) {
851				if (contact.getPgpKeyId() != 0) {
852					xmppService.getPgpEngine().hasKey(contact,
853							new UiCallback<Contact>() {
854
855								@Override
856								public void userInputRequried(PendingIntent pi,
857										Contact contact) {
858									activity.runIntent(
859											pi,
860											ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
861								}
862
863								@Override
864								public void success(Contact contact) {
865									messageSent();
866									activity.encryptTextMessage(message);
867								}
868
869								@Override
870								public void error(int error, Contact contact) {
871
872								}
873							});
874
875				} else {
876					showNoPGPKeyDialog(false,
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			} else {
891				if (conversation.getMucOptions().pgpKeysInUse()) {
892					if (!conversation.getMucOptions().everybodyHasKeys()) {
893						Toast warning = Toast
894								.makeText(getActivity(),
895										R.string.missing_public_keys,
896										Toast.LENGTH_LONG);
897						warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
898						warning.show();
899					}
900					activity.encryptTextMessage(message);
901					messageSent();
902				} else {
903					showNoPGPKeyDialog(true,
904							new DialogInterface.OnClickListener() {
905
906								@Override
907								public void onClick(DialogInterface dialog,
908										int which) {
909									conversation
910											.setNextEncryption(Message.ENCRYPTION_NONE);
911									message.setEncryption(Message.ENCRYPTION_NONE);
912									xmppService.sendMessage(message);
913									messageSent();
914								}
915							});
916				}
917			}
918		} else {
919			activity.showInstallPgpDialog();
920		}
921	}
922
923	public void showNoPGPKeyDialog(boolean plural,
924			DialogInterface.OnClickListener listener) {
925		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
926		builder.setIconAttribute(android.R.attr.alertDialogIcon);
927		if (plural) {
928			builder.setTitle(getString(R.string.no_pgp_keys));
929			builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
930		} else {
931			builder.setTitle(getString(R.string.no_pgp_key));
932			builder.setMessage(getText(R.string.contact_has_no_pgp_key));
933		}
934		builder.setNegativeButton(getString(R.string.cancel), null);
935		builder.setPositiveButton(getString(R.string.send_unencrypted),
936				listener);
937		builder.create().show();
938	}
939
940	protected void sendOtrMessage(final Message message) {
941		final ConversationActivity activity = (ConversationActivity) getActivity();
942		final XmppConnectionService xmppService = activity.xmppConnectionService;
943		if (conversation.hasValidOtrSession()) {
944			activity.xmppConnectionService.sendMessage(message);
945			messageSent();
946		} else {
947			activity.selectPresence(message.getConversation(),
948					new OnPresenceSelected() {
949
950						@Override
951						public void onPresenceSelected() {
952							message.setPresence(conversation.getNextPresence());
953							xmppService.sendMessage(message);
954							messageSent();
955						}
956					});
957		}
958	}
959
960	private static class ViewHolder {
961
962		protected LinearLayout message_box;
963		protected Button download_button;
964		protected ImageView image;
965		protected ImageView indicator;
966		protected TextView time;
967		protected TextView messageBody;
968		protected ImageView contact_picture;
969
970	}
971
972	private class BitmapCache {
973		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
974
975		public Bitmap get(String name, Contact contact, Context context) {
976			if (bitmaps.containsKey(name)) {
977				return bitmaps.get(name);
978			} else {
979				Bitmap bm;
980				if (contact != null) {
981					bm = UIHelper
982							.getContactPicture(contact, 48, context, false);
983				} else {
984					bm = UIHelper.getContactPicture(name, 48, context, false);
985				}
986				bitmaps.put(name, bm);
987				return bm;
988			}
989		}
990	}
991
992	public void setText(String text) {
993		this.pastedText = text;
994	}
995
996	public void clearInputField() {
997		this.chatMsg.setText("");
998	}
999}