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