ConversationFragment.java

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