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			if (this.conversation.getMessages().size() == 0) {
687				this.messageList.clear();
688			} else {
689				for(Message message : this.conversation.getMessages()) {
690					if (!this.messageList.contains(message)) {
691						this.messageList.add(message);
692					}
693				}
694				updateStatusMessages();
695			}
696			this.messageListAdapter.notifyDataSetChanged();
697			if (conversation.getMode() == Conversation.MODE_SINGLE) {
698				if (messageList.size() >= 1) {
699					makeFingerprintWarning(conversation.getLatestEncryption());
700				}
701			} else {
702				if (conversation.getMucOptions().getError() != 0) {
703					mucError.setVisibility(View.VISIBLE);
704					if (conversation.getMucOptions().getError() == MucOptions.ERROR_NICK_IN_USE) {
705						mucErrorText.setText(getString(R.string.nick_in_use));
706					}
707				} else {
708					mucError.setVisibility(View.GONE);
709				}
710			}
711			getActivity().invalidateOptionsMenu();
712			updateChatMsgHint();
713			if (!activity.shouldPaneBeOpen()) {
714				activity.xmppConnectionService.markRead(conversation);
715				// TODO update notifications
716				UIHelper.updateNotification(getActivity(),
717						activity.getConversationList(), null, false);
718				activity.updateConversationList();
719			}
720		}
721	}
722
723	private void messageSent() {
724		int size = this.messageList.size();
725		if (size >= 1) {
726			messagesView.setSelection(size - 1);
727		}
728		chatMsg.setText("");
729	}
730	
731	protected void updateStatusMessages() {
732		boolean addedStatusMsg = false;
733		if (conversation.getMode() == Conversation.MODE_SINGLE) {
734			for (int i = this.messageList.size() - 1; i >= 0; --i) {
735				if (addedStatusMsg) {
736					if (this.messageList.get(i).getType() == Message.TYPE_STATUS) {
737						this.messageList.remove(i);
738						--i;
739					}
740				} else {
741					if (this.messageList.get(i).getStatus() == Message.STATUS_RECIEVED) {
742						addedStatusMsg = true;
743					} else {
744						if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
745							this.messageList.add(i + 1,
746									Message.createStatusMessage(conversation));
747							addedStatusMsg = true;
748						}
749					}
750				}
751			}
752		}
753	}
754
755	protected void makeFingerprintWarning(int latestEncryption) {
756		final LinearLayout fingerprintWarning = (LinearLayout) getView()
757				.findViewById(R.id.new_fingerprint);
758		Set<String> knownFingerprints = conversation.getContact()
759				.getOtrFingerprints();
760		if ((latestEncryption == Message.ENCRYPTION_OTR)
761				&& (conversation.hasValidOtrSession()
762						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
763							.contains(conversation.getOtrFingerprint())))) {
764			fingerprintWarning.setVisibility(View.VISIBLE);
765			TextView fingerprint = (TextView) getView().findViewById(
766					R.id.otr_fingerprint);
767			fingerprint.setText(conversation.getOtrFingerprint());
768			fingerprintWarning.setOnClickListener(new OnClickListener() {
769
770				@Override
771				public void onClick(View v) {
772					AlertDialog dialog = UIHelper.getVerifyFingerprintDialog(
773							(ConversationActivity) getActivity(), conversation,
774							fingerprintWarning);
775					dialog.show();
776				}
777			});
778		} else {
779			fingerprintWarning.setVisibility(View.GONE);
780		}
781	}
782
783	protected void sendPlainTextMessage(Message message) {
784		ConversationActivity activity = (ConversationActivity) getActivity();
785		activity.xmppConnectionService.sendMessage(message);
786		messageSent();
787	}
788
789	protected void sendPgpMessage(final Message message) {
790		final ConversationActivity activity = (ConversationActivity) getActivity();
791		final XmppConnectionService xmppService = activity.xmppConnectionService;
792		final Contact contact = message.getConversation().getContact();
793		if (activity.hasPgp()) {
794			if (conversation.getMode() == Conversation.MODE_SINGLE) {
795				if (contact.getPgpKeyId() != 0) {
796					xmppService.getPgpEngine().hasKey(contact,
797							new UiCallback<Contact>() {
798
799								@Override
800								public void userInputRequried(PendingIntent pi,
801										Contact contact) {
802									activity.runIntent(
803											pi,
804											ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
805								}
806
807								@Override
808								public void success(Contact contact) {
809									messageSent();
810									activity.encryptTextMessage(message);
811								}
812
813								@Override
814								public void error(int error, Contact contact) {
815
816								}
817							});
818
819				} else {
820					showNoPGPKeyDialog(false,
821							new DialogInterface.OnClickListener() {
822
823								@Override
824								public void onClick(DialogInterface dialog,
825										int which) {
826									conversation
827											.setNextEncryption(Message.ENCRYPTION_NONE);
828									message.setEncryption(Message.ENCRYPTION_NONE);
829									xmppService.sendMessage(message);
830									messageSent();
831								}
832							});
833				}
834			} else {
835				if (conversation.getMucOptions().pgpKeysInUse()) {
836					if (!conversation.getMucOptions().everybodyHasKeys()) {
837						Toast warning = Toast
838								.makeText(getActivity(),
839										R.string.missing_public_keys,
840										Toast.LENGTH_LONG);
841						warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
842						warning.show();
843					}
844					activity.encryptTextMessage(message);
845				} else {
846					showNoPGPKeyDialog(true,
847							new DialogInterface.OnClickListener() {
848
849								@Override
850								public void onClick(DialogInterface dialog,
851										int which) {
852									conversation
853											.setNextEncryption(Message.ENCRYPTION_NONE);
854									message.setEncryption(Message.ENCRYPTION_NONE);
855									xmppService.sendMessage(message);
856									messageSent();
857								}
858							});
859				}
860			}
861		} else {
862			activity.showInstallPgpDialog();
863		}
864	}
865
866	public void showNoPGPKeyDialog(boolean plural,
867			DialogInterface.OnClickListener listener) {
868		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
869		builder.setIconAttribute(android.R.attr.alertDialogIcon);
870		if (plural) {
871			builder.setTitle(getString(R.string.no_pgp_keys));
872			builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
873		} else {
874			builder.setTitle(getString(R.string.no_pgp_key));
875			builder.setMessage(getText(R.string.contact_has_no_pgp_key));
876		}
877		builder.setNegativeButton(getString(R.string.cancel), null);
878		builder.setPositiveButton(getString(R.string.send_unencrypted),
879				listener);
880		builder.create().show();
881	}
882
883	protected void sendOtrMessage(final Message message) {
884		final ConversationActivity activity = (ConversationActivity) getActivity();
885		final XmppConnectionService xmppService = activity.xmppConnectionService;
886		if (conversation.hasValidOtrSession()) {
887			activity.xmppConnectionService.sendMessage(message);
888			messageSent();
889		} else {
890			activity.selectPresence(message.getConversation(),
891					new OnPresenceSelected() {
892
893						@Override
894						public void onPresenceSelected(boolean success,
895								String presence) {
896							if (success) {
897								message.setPresence(presence);
898								xmppService.sendMessage(message);
899								messageSent();
900							}
901						}
902
903						@Override
904						public void onSendPlainTextInstead() {
905							message.setEncryption(Message.ENCRYPTION_NONE);
906							xmppService.sendMessage(message);
907							messageSent();
908						}
909					});
910		}
911	}
912
913	private static class ViewHolder {
914
915		protected LinearLayout message_box;
916		protected Button download_button;
917		protected ImageView image;
918		protected ImageView indicator;
919		protected TextView time;
920		protected TextView messageBody;
921		protected ImageView contact_picture;
922
923	}
924
925	private class BitmapCache {
926		private HashMap<String, Bitmap> bitmaps = new HashMap<String, Bitmap>();
927
928		public Bitmap get(String name, Contact contact, Context context) {
929			if (bitmaps.containsKey(name)) {
930				return bitmaps.get(name);
931			} else {
932				Bitmap bm;
933				if (contact != null) {
934					bm = UIHelper
935							.getContactPicture(contact, 48, context, false);
936				} else {
937					bm = UIHelper.getContactPicture(name, 48, context, false);
938				}
939				bitmaps.put(name, bm);
940				return bm;
941			}
942		}
943	}
944
945	public void setText(String text) {
946		this.pastedText = text;
947	}
948
949	public void clearInputField() {
950		this.chatMsg.setText("");
951	}
952}