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