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