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