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				if (viewHolder.download_button != null) {
320					viewHolder.download_button.setVisibility(View.GONE);
321				}
322				viewHolder.image.setVisibility(View.GONE);
323				viewHolder.messageBody.setVisibility(View.VISIBLE);
324				viewHolder.messageBody
325						.setText(getString(R.string.decryption_failed));
326				viewHolder.messageBody.setTextColor(0xFFe92727);
327				viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
328				viewHolder.messageBody.setTextIsSelectable(false);
329			}
330
331			private void displayTextMessage(ViewHolder viewHolder, String text) {
332				if (viewHolder.download_button != null) {
333					viewHolder.download_button.setVisibility(View.GONE);
334				}
335				viewHolder.image.setVisibility(View.GONE);
336				viewHolder.messageBody.setVisibility(View.VISIBLE);
337				if (text != null) {
338					viewHolder.messageBody.setText(text.trim());
339				} else {
340					viewHolder.messageBody.setText("");
341				}
342				viewHolder.messageBody.setTextColor(0xff333333);
343				viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
344				viewHolder.messageBody.setTextIsSelectable(true);
345			}
346
347			private void displayImageMessage(ViewHolder viewHolder,
348					final Message message) {
349				if (viewHolder.download_button != null) {
350					viewHolder.download_button.setVisibility(View.GONE);
351				}
352				viewHolder.messageBody.setVisibility(View.GONE);
353				viewHolder.image.setVisibility(View.VISIBLE);
354				String[] fileParams = message.getBody().split(",");
355				if (fileParams.length == 3) {
356					double target = metrics.density * 288;
357					int w = Integer.parseInt(fileParams[1]);
358					int h = Integer.parseInt(fileParams[2]);
359					int scalledW;
360					int scalledH;
361					if (w <= h) {
362						scalledW = (int) (w / ((double) h / target));
363						scalledH = (int) target;
364					} else {
365						scalledW = (int) target;
366						scalledH = (int) (h / ((double) w / target));
367					}
368					viewHolder.image
369							.setLayoutParams(new LinearLayout.LayoutParams(
370									scalledW, scalledH));
371				}
372				activity.loadBitmap(message, viewHolder.image);
373				viewHolder.image.setOnClickListener(new OnClickListener() {
374
375					@Override
376					public void onClick(View v) {
377						Intent intent = new Intent(Intent.ACTION_VIEW);
378						intent.setDataAndType(
379								ImageProvider.getContentUri(message), "image/*");
380						startActivity(intent);
381					}
382				});
383				viewHolder.image
384						.setOnLongClickListener(new OnLongClickListener() {
385
386							@Override
387							public boolean onLongClick(View v) {
388								Intent shareIntent = new Intent();
389								shareIntent.setAction(Intent.ACTION_SEND);
390								shareIntent.putExtra(Intent.EXTRA_STREAM,
391										ImageProvider.getContentUri(message));
392								shareIntent.setType("image/webp");
393								startActivity(Intent.createChooser(shareIntent,
394										getText(R.string.share_with)));
395								return true;
396							}
397						});
398			}
399
400			@Override
401			public View getView(int position, View view, ViewGroup parent) {
402				final Message item = getItem(position);
403				int type = getItemViewType(position);
404				ViewHolder viewHolder;
405				if (view == null) {
406					viewHolder = new ViewHolder();
407					switch (type) {
408					case SENT:
409						view = (View) inflater.inflate(R.layout.message_sent,
410								null);
411						viewHolder.message_box = (LinearLayout) view
412								.findViewById(R.id.message_box);
413						viewHolder.contact_picture = (ImageView) view
414								.findViewById(R.id.message_photo);
415						viewHolder.contact_picture.setImageBitmap(selfBitmap);
416						viewHolder.indicator = (ImageView) view
417								.findViewById(R.id.security_indicator);
418						viewHolder.image = (ImageView) view
419								.findViewById(R.id.message_image);
420						viewHolder.messageBody = (TextView) view
421								.findViewById(R.id.message_body);
422						viewHolder.time = (TextView) view
423								.findViewById(R.id.message_time);
424						view.setTag(viewHolder);
425						break;
426					case RECIEVED:
427						view = (View) inflater.inflate(
428								R.layout.message_recieved, null);
429						viewHolder.message_box = (LinearLayout) view
430								.findViewById(R.id.message_box);
431						viewHolder.contact_picture = (ImageView) view
432								.findViewById(R.id.message_photo);
433
434						viewHolder.download_button = (Button) view
435								.findViewById(R.id.download_button);
436
437						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
438
439							viewHolder.contact_picture
440									.setImageBitmap(mBitmapCache.get(
441											item.getConversation().getName(
442													useSubject), item
443													.getConversation()
444													.getContact(),
445											getActivity()
446													.getApplicationContext()));
447
448						}
449						viewHolder.indicator = (ImageView) view
450								.findViewById(R.id.security_indicator);
451						viewHolder.image = (ImageView) view
452								.findViewById(R.id.message_image);
453						viewHolder.messageBody = (TextView) view
454								.findViewById(R.id.message_body);
455						viewHolder.time = (TextView) view
456								.findViewById(R.id.message_time);
457						view.setTag(viewHolder);
458						break;
459					case STATUS:
460						view = (View) inflater.inflate(R.layout.message_status,
461								null);
462						viewHolder.contact_picture = (ImageView) view
463								.findViewById(R.id.message_photo);
464						if (item.getConversation().getMode() == Conversation.MODE_SINGLE) {
465
466							viewHolder.contact_picture
467									.setImageBitmap(mBitmapCache.get(
468											item.getConversation().getName(
469													useSubject), item
470													.getConversation()
471													.getContact(),
472											getActivity()
473													.getApplicationContext()));
474							viewHolder.contact_picture.setAlpha(128);
475
476						}
477						break;
478					default:
479						viewHolder = null;
480						break;
481					}
482				} else {
483					viewHolder = (ViewHolder) view.getTag();
484				}
485
486				if (type == STATUS) {
487					return view;
488				}
489
490				if (type == RECIEVED) {
491					if (item.getConversation().getMode() == Conversation.MODE_MULTI) {
492						viewHolder.contact_picture.setImageBitmap(mBitmapCache
493								.get(item.getCounterpart(), null, getActivity()
494										.getApplicationContext()));
495						viewHolder.contact_picture
496								.setOnClickListener(new OnClickListener() {
497
498									@Override
499									public void onClick(View v) {
500										highlightInConference(item
501												.getCounterpart());
502									}
503								});
504					}
505				}
506
507				if (item.getType() == Message.TYPE_IMAGE) {
508					if (item.getStatus() == Message.STATUS_RECIEVING) {
509						displayInfoMessage(viewHolder, R.string.receiving_image);
510					} else if (item.getStatus() == Message.STATUS_RECEIVED_OFFER) {
511						viewHolder.image.setVisibility(View.GONE);
512						viewHolder.messageBody.setVisibility(View.GONE);
513						viewHolder.download_button.setVisibility(View.VISIBLE);
514						viewHolder.download_button
515								.setOnClickListener(new OnClickListener() {
516
517									@Override
518									public void onClick(View v) {
519										JingleConnection connection = item
520												.getJingleConnection();
521										if (connection != null) {
522											connection.accept();
523										} else {
524											Log.d("xmppService",
525													"attached jingle connection was null");
526										}
527									}
528								});
529					} else if ((item.getEncryption() == Message.ENCRYPTION_DECRYPTED)
530							|| (item.getEncryption() == Message.ENCRYPTION_NONE)
531							|| (item.getEncryption() == Message.ENCRYPTION_OTR)) {
532						displayImageMessage(viewHolder, item);
533					} else if (item.getEncryption() == Message.ENCRYPTION_PGP) {
534						displayInfoMessage(viewHolder,
535								R.string.encrypted_message);
536					} else {
537						displayDecryptionFailed(viewHolder);
538					}
539				} else {
540					if (item.getEncryption() == Message.ENCRYPTION_PGP) {
541						if (activity.hasPgp()) {
542							displayInfoMessage(viewHolder,
543									R.string.encrypted_message);
544						} else {
545							displayInfoMessage(viewHolder,
546									R.string.install_openkeychain);
547							viewHolder.message_box
548									.setOnClickListener(new OnClickListener() {
549
550										@Override
551										public void onClick(View v) {
552											activity.showInstallPgpDialog();
553										}
554									});
555						}
556					} else if (item.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
557						displayDecryptionFailed(viewHolder);
558					} else {
559						displayTextMessage(viewHolder, item.getBody());
560					}
561				}
562
563				displayStatus(viewHolder, item);
564
565				return view;
566			}
567		};
568		messagesView.setAdapter(messageListAdapter);
569
570		return view;
571	}
572
573	protected void highlightInConference(String nick) {
574		String oldString = chatMsg.getText().toString().trim();
575		if (oldString.isEmpty()) {
576			chatMsg.setText(nick + ": ");
577		} else {
578			chatMsg.setText(oldString + " " + nick + " ");
579		}
580		int position = chatMsg.length();
581		Editable etext = chatMsg.getText();
582		Selection.setSelection(etext, position);
583	}
584
585	protected Bitmap findSelfPicture() {
586		SharedPreferences sharedPref = PreferenceManager
587				.getDefaultSharedPreferences(getActivity()
588						.getApplicationContext());
589		boolean showPhoneSelfContactPicture = sharedPref.getBoolean(
590				"show_phone_selfcontact_picture", true);
591
592		return UIHelper.getSelfContactPicture(conversation.getAccount(), 48,
593				showPhoneSelfContactPicture, getActivity());
594	}
595
596	@Override
597	public void onStart() {
598		super.onStart();
599		this.activity = (ConversationActivity) getActivity();
600		SharedPreferences preferences = PreferenceManager
601				.getDefaultSharedPreferences(activity);
602		this.useSubject = preferences.getBoolean("use_subject_in_muc", true);
603		if (activity.xmppConnectionServiceBound) {
604			this.onBackendConnected();
605		}
606	}
607
608	@Override
609	public void onStop() {
610		super.onStop();
611		if (this.conversation != null) {
612			this.conversation.setNextMessage(chatMsg.getText().toString());
613		}
614	}
615
616	public void onBackendConnected() {
617		this.conversation = activity.getSelectedConversation();
618		if (this.conversation == null) {
619			return;
620		}
621		String oldString = conversation.getNextMessage().trim();
622		if (this.pastedText == null) {
623			this.chatMsg.setText(oldString);
624		} else {
625
626			if (oldString.isEmpty()) {
627				chatMsg.setText(pastedText);
628			} else {
629				chatMsg.setText(oldString + " " + pastedText);
630			}
631			pastedText = null;
632		}
633		int position = chatMsg.length();
634		Editable etext = chatMsg.getText();
635		Selection.setSelection(etext, position);
636		this.selfBitmap = findSelfPicture();
637		updateMessages();
638		if (activity.getSlidingPaneLayout().isSlideable()) {
639			if (!activity.shouldPaneBeOpen()) {
640				activity.getSlidingPaneLayout().closePane();
641				activity.getActionBar().setDisplayHomeAsUpEnabled(true);
642				activity.getActionBar().setHomeButtonEnabled(true);
643				activity.getActionBar().setTitle(
644						conversation.getName(useSubject));
645				activity.invalidateOptionsMenu();
646			}
647		}
648		if (conversation.getMode() == Conversation.MODE_MULTI) {
649			activity.xmppConnectionService
650					.setOnRenameListener(new OnRenameListener() {
651
652						@Override
653						public void onRename(final boolean success) {
654							activity.xmppConnectionService
655									.updateConversation(conversation);
656							getActivity().runOnUiThread(new Runnable() {
657
658								@Override
659								public void run() {
660									if (success) {
661										Toast.makeText(
662												getActivity(),
663												getString(R.string.your_nick_has_been_changed),
664												Toast.LENGTH_SHORT).show();
665									} else {
666										Toast.makeText(
667												getActivity(),
668												getString(R.string.nick_in_use),
669												Toast.LENGTH_SHORT).show();
670									}
671								}
672							});
673						}
674					});
675		}
676	}
677
678	private void decryptMessage(Message message) {
679		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
680		if (engine != null) {
681			engine.decrypt(message, new UiCallback<Message>() {
682
683				@Override
684				public void userInputRequried(PendingIntent pi, Message message) {
685					askForPassphraseIntent = pi.getIntentSender();
686					pgpInfo.setVisibility(View.VISIBLE);
687				}
688
689				@Override
690				public void success(Message message) {
691					activity.xmppConnectionService.databaseBackend
692							.updateMessage(message);
693					updateMessages();
694				}
695
696				@Override
697				public void error(int error, Message message) {
698					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
699					// updateMessages();
700				}
701			});
702		} else {
703			pgpInfo.setVisibility(View.GONE);
704		}
705	}
706
707	public void updateMessages() {
708		if (getView() == null) {
709			return;
710		}
711		ConversationActivity activity = (ConversationActivity) getActivity();
712		if (this.conversation != null) {
713			for (Message message : this.conversation.getMessages()) {
714				if ((message.getEncryption() == Message.ENCRYPTION_PGP)
715						&& ((message.getStatus() == Message.STATUS_RECIEVED) || (message
716								.getStatus() == Message.STATUS_SEND))) {
717					decryptMessage(message);
718					break;
719				}
720			}
721			if (this.conversation.getMessages().size() == 0) {
722				this.messageList.clear();
723				messagesLoaded = false;
724			} else {
725				for (Message message : this.conversation.getMessages()) {
726					if (!this.messageList.contains(message)) {
727						this.messageList.add(message);
728					}
729				}
730				messagesLoaded = true;
731				updateStatusMessages();
732			}
733			this.messageListAdapter.notifyDataSetChanged();
734			if (conversation.getMode() == Conversation.MODE_SINGLE) {
735				if (messageList.size() >= 1) {
736					makeFingerprintWarning(conversation.getLatestEncryption());
737				}
738			} else {
739				if (conversation.getMucOptions().getError() != 0) {
740					mucError.setVisibility(View.VISIBLE);
741					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
742						mucErrorText.setText(getString(R.string.nick_in_use));
743					}
744				} else {
745					mucError.setVisibility(View.GONE);
746				}
747			}
748			getActivity().invalidateOptionsMenu();
749			updateChatMsgHint();
750			if (!activity.shouldPaneBeOpen()) {
751				activity.xmppConnectionService.markRead(conversation);
752				// TODO update notifications
753				UIHelper.updateNotification(getActivity(),
754						activity.getConversationList(), null, false);
755				activity.updateConversationList();
756			}
757		}
758	}
759
760	private void messageSent() {
761		int size = this.messageList.size();
762		if (size >= 1) {
763			messagesView.setSelection(size - 1);
764		}
765		chatMsg.setText("");
766	}
767
768	protected void updateStatusMessages() {
769		boolean addedStatusMsg = false;
770		if (conversation.getMode() == Conversation.MODE_SINGLE) {
771			for (int i = this.messageList.size() - 1; i >= 0; --i) {
772				if (addedStatusMsg) {
773					if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
774						this.messageList.remove(i);
775						--i;
776					}
777				} else {
778					if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
779						addedStatusMsg = true;
780					} else {
781						if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
782							this.messageList.add(i + 1,
783									Message.createStatusMessage(conversation));
784							addedStatusMsg = true;
785						}
786					}
787				}
788			}
789		}
790	}
791
792	protected void makeFingerprintWarning(int latestEncryption) {
793		final LinearLayout fingerprintWarning = (LinearLayout) getView()
794				.findViewById(R.id.new_fingerprint);
795		Set<String> knownFingerprints = conversation.getContact()
796				.getOtrFingerprints();
797		if ((latestEncryption == Message.ENCRYPTION_OTR)
798				&& (conversation.hasValidOtrSession()
799						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
800							.contains(conversation.getOtrFingerprint())))) {
801			fingerprintWarning.setVisibility(View.VISIBLE);
802			TextView fingerprint = (TextView) getView().findViewById(
803					R.id.otr_fingerprint);
804			fingerprint.setText(conversation.getOtrFingerprint());
805			fingerprintWarning.setOnClickListener(new OnClickListener() {
806
807				@Override
808				public void onClick(View v) {
809					AlertDialog dialog = UIHelper.getVerifyFingerprintDialog(
810							(ConversationActivity) getActivity(), conversation,
811							fingerprintWarning);
812					dialog.show();
813				}
814			});
815		} else {
816			fingerprintWarning.setVisibility(View.GONE);
817		}
818	}
819
820	protected void sendPlainTextMessage(Message message) {
821		ConversationActivity activity = (ConversationActivity) getActivity();
822		activity.xmppConnectionService.sendMessage(message);
823		messageSent();
824	}
825
826	protected void sendPgpMessage(final Message message) {
827		final ConversationActivity activity = (ConversationActivity) getActivity();
828		final XmppConnectionService xmppService = activity.xmppConnectionService;
829		final Contact contact = message.getConversation().getContact();
830		if (activity.hasPgp()) {
831			if (conversation.getMode() == Conversation.MODE_SINGLE) {
832				if (contact.getPgpKeyId() != 0) {
833					xmppService.getPgpEngine().hasKey(contact,
834							new UiCallback<Contact>() {
835
836								@Override
837								public void userInputRequried(PendingIntent pi,
838										Contact contact) {
839									activity.runIntent(
840											pi,
841											ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
842								}
843
844								@Override
845								public void success(Contact contact) {
846									messageSent();
847									activity.encryptTextMessage(message);
848								}
849
850								@Override
851								public void error(int error, Contact contact) {
852
853								}
854							});
855
856				} else {
857					showNoPGPKeyDialog(false,
858							new DialogInterface.OnClickListener() {
859
860								@Override
861								public void onClick(DialogInterface dialog,
862										int which) {
863									conversation
864											.setNextEncryption(Message.ENCRYPTION_NONE);
865									message.setEncryption(Message.ENCRYPTION_NONE);
866									xmppService.sendMessage(message);
867									messageSent();
868								}
869							});
870				}
871			} else {
872				if (conversation.getMucOptions().pgpKeysInUse()) {
873					if (!conversation.getMucOptions().everybodyHasKeys()) {
874						Toast warning = Toast
875								.makeText(getActivity(),
876										R.string.missing_public_keys,
877										Toast.LENGTH_LONG);
878						warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
879						warning.show();
880					}
881					activity.encryptTextMessage(message);
882					messageSent();
883				} else {
884					showNoPGPKeyDialog(true,
885							new DialogInterface.OnClickListener() {
886
887								@Override
888								public void onClick(DialogInterface dialog,
889										int which) {
890									conversation
891											.setNextEncryption(Message.ENCRYPTION_NONE);
892									message.setEncryption(Message.ENCRYPTION_NONE);
893									xmppService.sendMessage(message);
894									messageSent();
895								}
896							});
897				}
898			}
899		} else {
900			activity.showInstallPgpDialog();
901		}
902	}
903
904	public void showNoPGPKeyDialog(boolean plural,
905			DialogInterface.OnClickListener listener) {
906		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
907		builder.setIconAttribute(android.R.attr.alertDialogIcon);
908		if (plural) {
909			builder.setTitle(getString(R.string.no_pgp_keys));
910			builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
911		} else {
912			builder.setTitle(getString(R.string.no_pgp_key));
913			builder.setMessage(getText(R.string.contact_has_no_pgp_key));
914		}
915		builder.setNegativeButton(getString(R.string.cancel), null);
916		builder.setPositiveButton(getString(R.string.send_unencrypted),
917				listener);
918		builder.create().show();
919	}
920
921	protected void sendOtrMessage(final Message message) {
922		final ConversationActivity activity = (ConversationActivity) getActivity();
923		final XmppConnectionService xmppService = activity.xmppConnectionService;
924		if (conversation.hasValidOtrSession()) {
925			activity.xmppConnectionService.sendMessage(message);
926			messageSent();
927		} else {
928			activity.selectPresence(message.getConversation(),
929					new OnPresenceSelected() {
930
931						@Override
932						public void onPresenceSelected() {
933							message.setPresence(conversation.getNextPresence());
934							xmppService.sendMessage(message);
935							messageSent();
936						}
937					});
938		}
939	}
940
941	private static class ViewHolder {
942
943		protected LinearLayout message_box;
944		protected Button download_button;
945		protected ImageView image;
946		protected ImageView indicator;
947		protected TextView time;
948		protected TextView messageBody;
949		protected ImageView contact_picture;
950
951	}
952
953	private class BitmapCache {
954		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
955
956		public Bitmap get(String name, Contact contact, Context context) {
957			if (bitmaps.containsKey(name)) {
958				return bitmaps.get(name);
959			} else {
960				Bitmap bm;
961				if (contact != null) {
962					bm = UIHelper
963							.getContactPicture(contact, 48, context, false);
964				} else {
965					bm = UIHelper.getContactPicture(name, 48, context, false);
966				}
967				bitmaps.put(name, bm);
968				return bm;
969			}
970		}
971	}
972
973	public void setText(String text) {
974		this.pastedText = text;
975	}
976
977	public void clearInputField() {
978		this.chatMsg.setText("");
979	}
980}