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