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