ConversationFragment.java

  1package eu.siacs.conversations.ui;
  2
  3import java.util.ArrayList;
  4import java.util.List;
  5import java.util.Set;
  6import java.util.concurrent.ConcurrentLinkedQueue;
  7
  8import net.java.otr4j.session.SessionStatus;
  9import eu.siacs.conversations.R;
 10import eu.siacs.conversations.crypto.PgpEngine;
 11import eu.siacs.conversations.entities.Account;
 12import eu.siacs.conversations.entities.Contact;
 13import eu.siacs.conversations.entities.Conversation;
 14import eu.siacs.conversations.entities.Message;
 15import eu.siacs.conversations.entities.MucOptions;
 16import eu.siacs.conversations.entities.Presences;
 17import eu.siacs.conversations.services.XmppConnectionService;
 18import eu.siacs.conversations.ui.EditMessage.OnEnterPressed;
 19import eu.siacs.conversations.ui.XmppActivity.OnPresenceSelected;
 20import eu.siacs.conversations.ui.XmppActivity.OnValueEdited;
 21import eu.siacs.conversations.ui.adapter.MessageAdapter;
 22import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureClicked;
 23import eu.siacs.conversations.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
 24import eu.siacs.conversations.utils.UIHelper;
 25import android.app.AlertDialog;
 26import android.app.Fragment;
 27import android.app.PendingIntent;
 28import android.content.Context;
 29import android.content.DialogInterface;
 30import android.content.Intent;
 31import android.content.IntentSender;
 32import android.content.IntentSender.SendIntentException;
 33import android.os.Bundle;
 34import android.text.Editable;
 35import android.text.Selection;
 36import android.view.ContextMenu;
 37import android.view.ContextMenu.ContextMenuInfo;
 38import android.view.Gravity;
 39import android.view.KeyEvent;
 40import android.view.LayoutInflater;
 41import android.view.MenuItem;
 42import android.view.View;
 43import android.view.View.OnClickListener;
 44import android.view.ViewGroup;
 45import android.view.inputmethod.EditorInfo;
 46import android.view.inputmethod.InputMethodManager;
 47import android.widget.AbsListView.OnScrollListener;
 48import android.widget.TextView.OnEditorActionListener;
 49import android.widget.AbsListView;
 50
 51import android.widget.AdapterView;
 52import android.widget.AdapterView.AdapterContextMenuInfo;
 53import android.widget.ListView;
 54import android.widget.ImageButton;
 55import android.widget.RelativeLayout;
 56import android.widget.TextView;
 57import android.widget.Toast;
 58
 59public class ConversationFragment extends Fragment {
 60
 61	protected Conversation conversation;
 62	protected ListView messagesView;
 63	protected LayoutInflater inflater;
 64	protected List<Message> messageList = new ArrayList<Message>();
 65	protected MessageAdapter messageListAdapter;
 66	protected Contact contact;
 67
 68	protected String queuedPqpMessage = null;
 69
 70	private EditMessage mEditMessage;
 71	private ImageButton mSendButton;
 72	private String pastedText = null;
 73	private RelativeLayout snackbar;
 74	private TextView snackbarMessage;
 75	private TextView snackbarAction;
 76
 77	private boolean messagesLoaded = false;
 78
 79	private IntentSender askForPassphraseIntent = null;
 80
 81	private ConcurrentLinkedQueue<Message> mEncryptedMessages = new ConcurrentLinkedQueue<Message>();
 82	private boolean mDecryptJobRunning = false;
 83
 84	private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
 85
 86		@Override
 87		public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
 88			if (actionId == EditorInfo.IME_ACTION_SEND) {
 89				InputMethodManager imm = (InputMethodManager) v.getContext()
 90						.getSystemService(Context.INPUT_METHOD_SERVICE);
 91				imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
 92				sendMessage();
 93				return true;
 94			} else {
 95				return false;
 96			}
 97		}
 98	};
 99
100	private OnClickListener mSendButtonListener = new OnClickListener() {
101
102		@Override
103		public void onClick(View v) {
104			sendMessage();
105		}
106	};
107	protected OnClickListener clickToDecryptListener = new OnClickListener() {
108
109		@Override
110		public void onClick(View v) {
111			if (activity.hasPgp() && askForPassphraseIntent != null) {
112				try {
113					getActivity().startIntentSenderForResult(
114							askForPassphraseIntent,
115							ConversationActivity.REQUEST_DECRYPT_PGP, null, 0,
116							0, 0);
117				} catch (SendIntentException e) {
118					//
119				}
120			}
121		}
122	};
123
124	private OnClickListener clickToMuc = new OnClickListener() {
125
126		@Override
127		public void onClick(View v) {
128			Intent intent = new Intent(getActivity(),
129					ConferenceDetailsActivity.class);
130			intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
131			intent.putExtra("uuid", conversation.getUuid());
132			startActivity(intent);
133		}
134	};
135
136	private OnClickListener leaveMuc = new OnClickListener() {
137
138		@Override
139		public void onClick(View v) {
140			activity.endConversation(conversation);
141		}
142	};
143
144	private OnClickListener joinMuc = new OnClickListener() {
145
146		@Override
147		public void onClick(View v) {
148			activity.xmppConnectionService.joinMuc(conversation);
149		}
150	};
151
152	private OnClickListener enterPassword = new OnClickListener() {
153
154		@Override
155		public void onClick(View v) {
156			MucOptions muc = conversation.getMucOptions();
157			String password = muc.getPassword();
158			if (password == null) {
159				password = "";
160			}
161			activity.quickPasswordEdit(password, new OnValueEdited() {
162
163				@Override
164				public void onValueEdited(String value) {
165					activity.xmppConnectionService.providePasswordForMuc(
166							conversation, value);
167				}
168			});
169		}
170	};
171
172	private OnScrollListener mOnScrollListener = new OnScrollListener() {
173
174		@Override
175		public void onScrollStateChanged(AbsListView view, int scrollState) {
176			// TODO Auto-generated method stub
177
178		}
179
180		@Override
181		public void onScroll(AbsListView view, int firstVisibleItem,
182				int visibleItemCount, int totalItemCount) {
183			if (firstVisibleItem == 0 && messagesLoaded) {
184				long timestamp = messageList.get(0).getTimeSent();
185				messagesLoaded = false;
186				int size = activity.xmppConnectionService.loadMoreMessages(
187						conversation, timestamp);
188				messageList.clear();
189				messageList.addAll(conversation.getMessages());
190				updateStatusMessages();
191				messageListAdapter.notifyDataSetChanged();
192				if (size != 0) {
193					messagesLoaded = true;
194				}
195				messagesView.setSelectionFromTop(size + 1, 0);
196			}
197		}
198	};
199
200	private ConversationActivity activity;
201	private Message selectedMessage;
202
203	private void sendMessage() {
204		if (this.conversation == null) {
205			return;
206		}
207		if (mEditMessage.getText().length() < 1) {
208			if (this.conversation.getMode() == Conversation.MODE_MULTI) {
209				conversation.setNextPresence(null);
210				updateChatMsgHint();
211			}
212			return;
213		}
214		Message message = new Message(conversation, mEditMessage.getText()
215				.toString(), conversation.getNextEncryption(activity
216				.forceEncryption()));
217		if (conversation.getMode() == Conversation.MODE_MULTI) {
218			if (conversation.getNextPresence() != null) {
219				message.setPresence(conversation.getNextPresence());
220				message.setType(Message.TYPE_PRIVATE);
221				conversation.setNextPresence(null);
222			}
223		}
224		if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
225			sendOtrMessage(message);
226		} else if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_PGP) {
227			sendPgpMessage(message);
228		} else {
229			sendPlainTextMessage(message);
230		}
231	}
232
233	public void updateChatMsgHint() {
234		if (conversation.getMode() == Conversation.MODE_MULTI
235				&& conversation.getNextPresence() != null) {
236			this.mEditMessage.setHint(getString(
237					R.string.send_private_message_to,
238					conversation.getNextPresence()));
239		} else {
240			switch (conversation.getNextEncryption(activity.forceEncryption())) {
241			case Message.ENCRYPTION_NONE:
242				mEditMessage
243						.setHint(getString(R.string.send_plain_text_message));
244				break;
245			case Message.ENCRYPTION_OTR:
246				mEditMessage.setHint(getString(R.string.send_otr_message));
247				break;
248			case Message.ENCRYPTION_PGP:
249				mEditMessage.setHint(getString(R.string.send_pgp_message));
250				break;
251			default:
252				break;
253			}
254		}
255	}
256
257	@Override
258	public View onCreateView(final LayoutInflater inflater,
259			ViewGroup container, Bundle savedInstanceState) {
260		final View view = inflater.inflate(R.layout.fragment_conversation,
261				container, false);
262		mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
263		mEditMessage.setOnClickListener(new OnClickListener() {
264
265			@Override
266			public void onClick(View v) {
267				activity.hideConversationsOverview();
268			}
269		});
270		mEditMessage.setOnEditorActionListener(mEditorActionListener);
271		mEditMessage.setOnEnterPressedListener(new OnEnterPressed() {
272
273			@Override
274			public void onEnterPressed() {
275				sendMessage();
276			}
277		});
278
279		mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
280		mSendButton.setOnClickListener(this.mSendButtonListener);
281
282		snackbar = (RelativeLayout) view.findViewById(R.id.snackbar);
283		snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message);
284		snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
285
286		messagesView = (ListView) view.findViewById(R.id.messages_view);
287		messagesView.setOnScrollListener(mOnScrollListener);
288		messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
289		messageListAdapter = new MessageAdapter(
290				(ConversationActivity) getActivity(), this.messageList);
291		messageListAdapter
292				.setOnContactPictureClicked(new OnContactPictureClicked() {
293
294					@Override
295					public void onContactPictureClicked(Message message) {
296						if (message.getStatus() <= Message.STATUS_RECEIVED) {
297							if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
298								if (message.getPresence() != null) {
299									highlightInConference(message.getPresence());
300								} else {
301									highlightInConference(message
302											.getCounterpart());
303								}
304							} else {
305								Contact contact = message.getConversation()
306										.getContact();
307								if (contact.showInRoster()) {
308									activity.switchToContactDetails(contact);
309								} else {
310									activity.showAddToRosterDialog(message
311											.getConversation());
312								}
313							}
314						}
315					}
316				});
317		messageListAdapter
318				.setOnContactPictureLongClicked(new OnContactPictureLongClicked() {
319
320					@Override
321					public void onContactPictureLongClicked(Message message) {
322						if (message.getStatus() <= Message.STATUS_RECEIVED) {
323							if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
324								if (message.getPresence() != null) {
325									privateMessageWith(message.getPresence());
326								} else {
327									privateMessageWith(message.getCounterpart());
328								}
329							}
330						}
331					}
332				});
333		messagesView.setAdapter(messageListAdapter);
334
335		registerForContextMenu(messagesView);
336
337		return view;
338	}
339
340	@Override
341	public void onCreateContextMenu(ContextMenu menu, View v,
342			ContextMenuInfo menuInfo) {
343		super.onCreateContextMenu(menu, v, menuInfo);
344		AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
345		this.selectedMessage = this.messageList.get(acmi.position);
346		populateContextMenu(menu);
347	}
348
349	private void populateContextMenu(ContextMenu menu) {
350		if (this.selectedMessage.getType() != Message.TYPE_STATUS) {
351			activity.getMenuInflater().inflate(R.menu.message_context, menu);
352			menu.setHeaderTitle(R.string.message_options);
353			MenuItem copyText = menu.findItem(R.id.copy_text);
354			MenuItem shareImage = menu.findItem(R.id.share_image);
355			MenuItem sendAgain = menu.findItem(R.id.send_again);
356			MenuItem copyUrl = menu.findItem(R.id.copy_url);
357			MenuItem downloadImage = menu.findItem(R.id.download_image);
358			if (this.selectedMessage.getType() != Message.TYPE_TEXT
359					|| this.selectedMessage.getDownloadable() != null) {
360				copyText.setVisible(false);
361			}
362			if (this.selectedMessage.getType() != Message.TYPE_IMAGE
363					|| this.selectedMessage.getDownloadable() != null) {
364				shareImage.setVisible(false);
365			}
366			if (this.selectedMessage.getStatus() != Message.STATUS_SEND_FAILED) {
367				sendAgain.setVisible(false);
368			}
369			if ((this.selectedMessage.getType() != Message.TYPE_IMAGE && this.selectedMessage
370					.getDownloadable() == null)
371					|| this.selectedMessage.getImageParams().url == null) {
372				copyUrl.setVisible(false);
373			}
374
375			if (this.selectedMessage.getType() != Message.TYPE_TEXT
376					|| this.selectedMessage.getDownloadable() != null
377					|| !this.selectedMessage.bodyContainsDownloadable()) {
378				downloadImage.setVisible(false);
379			}
380		}
381	}
382
383	@Override
384	public boolean onContextItemSelected(MenuItem item) {
385		switch (item.getItemId()) {
386		case R.id.share_image:
387			shareImage(selectedMessage);
388			return true;
389		case R.id.copy_text:
390			copyText(selectedMessage);
391			return true;
392		case R.id.send_again:
393			resendMessage(selectedMessage);
394			return true;
395		case R.id.copy_url:
396			copyUrl(selectedMessage);
397			return true;
398		case R.id.download_image:
399			downloadImage(selectedMessage);
400			return true;
401		default:
402			return super.onContextItemSelected(item);
403		}
404	}
405
406	private void shareImage(Message message) {
407		Intent shareIntent = new Intent();
408		shareIntent.setAction(Intent.ACTION_SEND);
409		shareIntent.putExtra(Intent.EXTRA_STREAM,
410				activity.xmppConnectionService.getFileBackend()
411						.getJingleFileUri(message));
412		shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
413		shareIntent.setType("image/webp");
414		activity.startActivity(Intent.createChooser(shareIntent,
415				getText(R.string.share_with)));
416	}
417
418	private void copyText(Message message) {
419		if (activity.copyTextToClipboard(message.getMergedBody(),
420				R.string.message_text)) {
421			Toast.makeText(activity, R.string.message_copied_to_clipboard,
422					Toast.LENGTH_SHORT).show();
423		}
424	}
425
426	private void resendMessage(Message message) {
427		activity.xmppConnectionService.resendFailedMessages(message);
428	}
429
430	private void copyUrl(Message message) {
431		if (activity.copyTextToClipboard(
432				message.getImageParams().url.toString(), R.string.image_url)) {
433			Toast.makeText(activity, R.string.url_copied_to_clipboard,
434					Toast.LENGTH_SHORT).show();
435		}
436	}
437
438	private void downloadImage(Message message) {
439		activity.xmppConnectionService.getHttpConnectionManager()
440				.createNewConnection(message);
441	}
442
443	protected void privateMessageWith(String counterpart) {
444		this.mEditMessage.setText("");
445		this.conversation.setNextPresence(counterpart);
446		updateChatMsgHint();
447	}
448
449	protected void highlightInConference(String nick) {
450		String oldString = mEditMessage.getText().toString().trim();
451		if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) {
452			mEditMessage.getText().insert(0, nick + ": ");
453		} else {
454			if (mEditMessage.getText().charAt(
455					mEditMessage.getSelectionStart() - 1) != ' ') {
456				nick = " " + nick;
457			}
458			mEditMessage.getText().insert(mEditMessage.getSelectionStart(),
459					nick + " ");
460		}
461	}
462
463	@Override
464	public void onStart() {
465		super.onStart();
466		this.activity = (ConversationActivity) getActivity();
467		if (activity.xmppConnectionServiceBound) {
468			this.onBackendConnected();
469		}
470	}
471
472	@Override
473	public void onStop() {
474		mDecryptJobRunning = false;
475		super.onStop();
476		if (this.conversation != null) {
477			this.conversation.setNextMessage(mEditMessage.getText().toString());
478		}
479	}
480
481	public void onBackendConnected() {
482		this.activity = (ConversationActivity) getActivity();
483		this.conversation = activity.getSelectedConversation();
484		if (this.conversation == null) {
485			return;
486		}
487		String oldString = conversation.getNextMessage().trim();
488		if (this.pastedText == null) {
489			this.mEditMessage.setText(oldString);
490		} else {
491
492			if (oldString.isEmpty()) {
493				mEditMessage.setText(pastedText);
494			} else {
495				mEditMessage.setText(oldString + " " + pastedText);
496			}
497			pastedText = null;
498		}
499		int position = mEditMessage.length();
500		Editable etext = mEditMessage.getText();
501		Selection.setSelection(etext, position);
502		if (activity.isConversationsOverviewHideable()) {
503			if (!activity.shouldPaneBeOpen()) {
504				activity.hideConversationsOverview();
505				activity.openConversation(conversation);
506			}
507		}
508		if (this.conversation.getMode() == Conversation.MODE_MULTI) {
509			conversation.setNextPresence(null);
510		}
511		updateMessages();
512	}
513
514	public void updateMessages() {
515		if (getView() == null) {
516			return;
517		}
518		hideSnackbar();
519		final ConversationActivity activity = (ConversationActivity) getActivity();
520		if (this.conversation != null) {
521			final Contact contact = this.conversation.getContact();
522			if (this.conversation.isMuted()) {
523				showSnackbar(R.string.notifications_disabled, R.string.enable,
524						new OnClickListener() {
525
526							@Override
527							public void onClick(View v) {
528								conversation.setMutedTill(0);
529								activity.xmppConnectionService.databaseBackend
530										.updateConversation(conversation);
531								updateMessages();
532							}
533						});
534			} else if (!contact.showInRoster()
535					&& contact
536							.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
537				showSnackbar(R.string.contact_added_you, R.string.add_back,
538						new OnClickListener() {
539
540							@Override
541							public void onClick(View v) {
542								activity.xmppConnectionService
543										.createContact(contact);
544								activity.switchToContactDetails(contact);
545							}
546						});
547			}
548			for (Message message : this.conversation.getMessages()) {
549				if (message.getEncryption() == Message.ENCRYPTION_PGP
550						&& (message.getStatus() == Message.STATUS_RECEIVED || message
551								.getStatus() >= Message.STATUS_SEND)
552						&& message.getDownloadable() == null) {
553					if (!mEncryptedMessages.contains(message)) {
554						mEncryptedMessages.add(message);
555					}
556				}
557			}
558			decryptNext();
559			this.messageList.clear();
560			if (this.conversation.getMessages().size() == 0) {
561				messagesLoaded = false;
562			} else {
563				this.messageList.addAll(this.conversation.getMessages());
564				messagesLoaded = true;
565				updateStatusMessages();
566			}
567			this.messageListAdapter.notifyDataSetChanged();
568			if (conversation.getMode() == Conversation.MODE_SINGLE) {
569				if (messageList.size() >= 1) {
570					makeFingerprintWarning();
571				}
572			} else {
573				if (!conversation.getMucOptions().online()
574						&& conversation.getAccount().getStatus() == Account.STATUS_ONLINE) {
575					int error = conversation.getMucOptions().getError();
576					switch (error) {
577					case MucOptions.ERROR_NICK_IN_USE:
578						showSnackbar(R.string.nick_in_use, R.string.edit,
579								clickToMuc);
580						break;
581					case MucOptions.ERROR_ROOM_NOT_FOUND:
582						showSnackbar(R.string.conference_not_found,
583								R.string.leave, leaveMuc);
584						break;
585					case MucOptions.ERROR_PASSWORD_REQUIRED:
586						showSnackbar(R.string.conference_requires_password,
587								R.string.enter_password, enterPassword);
588						break;
589					case MucOptions.ERROR_BANNED:
590						showSnackbar(R.string.conference_banned,
591								R.string.leave, leaveMuc);
592						break;
593					case MucOptions.ERROR_MEMBERS_ONLY:
594						showSnackbar(R.string.conference_members_only,
595								R.string.leave, leaveMuc);
596						break;
597					case MucOptions.KICKED_FROM_ROOM:
598						showSnackbar(R.string.conference_kicked, R.string.join,
599								joinMuc);
600						break;
601					default:
602						break;
603					}
604				}
605			}
606			getActivity().invalidateOptionsMenu();
607			updateChatMsgHint();
608			if (!activity.shouldPaneBeOpen()) {
609				activity.xmppConnectionService.markRead(conversation, true);
610				activity.updateConversationList();
611			}
612			this.updateSendButton();
613		}
614	}
615
616	private void decryptNext() {
617		Message next = this.mEncryptedMessages.peek();
618		PgpEngine engine = activity.xmppConnectionService.getPgpEngine();
619
620		if (next != null && engine != null && !mDecryptJobRunning) {
621			mDecryptJobRunning = true;
622			engine.decrypt(next, new UiCallback<Message>() {
623
624				@Override
625				public void userInputRequried(PendingIntent pi, Message message) {
626					mDecryptJobRunning = false;
627					askForPassphraseIntent = pi.getIntentSender();
628					showSnackbar(R.string.openpgp_messages_found,
629							R.string.decrypt, clickToDecryptListener);
630				}
631
632				@Override
633				public void success(Message message) {
634					mDecryptJobRunning = false;
635					mEncryptedMessages.remove();
636					activity.xmppConnectionService.updateMessage(message);
637				}
638
639				@Override
640				public void error(int error, Message message) {
641					message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
642					mDecryptJobRunning = false;
643					mEncryptedMessages.remove();
644					activity.xmppConnectionService.updateConversationUi();
645				}
646			});
647		}
648	}
649
650	private void messageSent() {
651		int size = this.messageList.size();
652		messagesView.setSelection(size - 1);
653		mEditMessage.setText("");
654		updateChatMsgHint();
655	}
656
657	public void updateSendButton() {
658		Conversation c = this.conversation;
659		if (activity.useSendButtonToIndicateStatus() && c != null
660				&& c.getAccount().getStatus() == Account.STATUS_ONLINE) {
661			if (c.getMode() == Conversation.MODE_SINGLE) {
662				switch (c.getContact().getMostAvailableStatus()) {
663				case Presences.CHAT:
664					this.mSendButton
665							.setImageResource(R.drawable.ic_action_send_now_online);
666					break;
667				case Presences.ONLINE:
668					this.mSendButton
669							.setImageResource(R.drawable.ic_action_send_now_online);
670					break;
671				case Presences.AWAY:
672					this.mSendButton
673							.setImageResource(R.drawable.ic_action_send_now_away);
674					break;
675				case Presences.XA:
676					this.mSendButton
677							.setImageResource(R.drawable.ic_action_send_now_away);
678					break;
679				case Presences.DND:
680					this.mSendButton
681							.setImageResource(R.drawable.ic_action_send_now_dnd);
682					break;
683				default:
684					this.mSendButton
685							.setImageResource(R.drawable.ic_action_send_now_offline);
686					break;
687				}
688			} else if (c.getMode() == Conversation.MODE_MULTI) {
689				if (c.getMucOptions().online()) {
690					this.mSendButton
691							.setImageResource(R.drawable.ic_action_send_now_online);
692				} else {
693					this.mSendButton
694							.setImageResource(R.drawable.ic_action_send_now_offline);
695				}
696			} else {
697				this.mSendButton
698						.setImageResource(R.drawable.ic_action_send_now_offline);
699			}
700		} else {
701			this.mSendButton
702					.setImageResource(R.drawable.ic_action_send_now_offline);
703		}
704	}
705
706	protected void updateStatusMessages() {
707		if (conversation.getMode() == Conversation.MODE_SINGLE) {
708			for (int i = this.messageList.size() - 1; i >= 0; --i) {
709				if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
710					return;
711				} else {
712					if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
713						this.messageList.add(i + 1,
714								Message.createStatusMessage(conversation));
715						return;
716					}
717				}
718			}
719		}
720	}
721
722	protected void makeFingerprintWarning() {
723		Set<String> knownFingerprints = conversation.getContact()
724				.getOtrFingerprints();
725		if (conversation.hasValidOtrSession()
726						&& (!conversation.isMuted())
727						&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED) && (!knownFingerprints
728							.contains(conversation.getOtrFingerprint()))) {
729			showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify,
730					new OnClickListener() {
731
732						@Override
733						public void onClick(View v) {
734							if (conversation.getOtrFingerprint() != null) {
735								AlertDialog dialog = UIHelper
736										.getVerifyFingerprintDialog(
737												(ConversationActivity) getActivity(),
738												conversation, snackbar);
739								dialog.show();
740							}
741						}
742					});
743		}
744	}
745
746	protected void showSnackbar(int message, int action,
747			OnClickListener clickListener) {
748		snackbar.setVisibility(View.VISIBLE);
749		snackbar.setOnClickListener(null);
750		snackbarMessage.setText(message);
751		snackbarMessage.setOnClickListener(null);
752		snackbarAction.setText(action);
753		snackbarAction.setOnClickListener(clickListener);
754	}
755
756	protected void hideSnackbar() {
757		snackbar.setVisibility(View.GONE);
758	}
759
760	protected void sendPlainTextMessage(Message message) {
761		ConversationActivity activity = (ConversationActivity) getActivity();
762		activity.xmppConnectionService.sendMessage(message);
763		messageSent();
764	}
765
766	protected void sendPgpMessage(final Message message) {
767		final ConversationActivity activity = (ConversationActivity) getActivity();
768		final XmppConnectionService xmppService = activity.xmppConnectionService;
769		final Contact contact = message.getConversation().getContact();
770		if (activity.hasPgp()) {
771			if (conversation.getMode() == Conversation.MODE_SINGLE) {
772				if (contact.getPgpKeyId() != 0) {
773					xmppService.getPgpEngine().hasKey(contact,
774							new UiCallback<Contact>() {
775
776								@Override
777								public void userInputRequried(PendingIntent pi,
778										Contact contact) {
779									activity.runIntent(
780											pi,
781											ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
782								}
783
784								@Override
785								public void success(Contact contact) {
786									messageSent();
787									activity.encryptTextMessage(message);
788								}
789
790								@Override
791								public void error(int error, Contact contact) {
792
793								}
794							});
795
796				} else {
797					showNoPGPKeyDialog(false,
798							new DialogInterface.OnClickListener() {
799
800								@Override
801								public void onClick(DialogInterface dialog,
802										int which) {
803									conversation
804											.setNextEncryption(Message.ENCRYPTION_NONE);
805									xmppService.databaseBackend
806											.updateConversation(conversation);
807									message.setEncryption(Message.ENCRYPTION_NONE);
808									xmppService.sendMessage(message);
809									messageSent();
810								}
811							});
812				}
813			} else {
814				if (conversation.getMucOptions().pgpKeysInUse()) {
815					if (!conversation.getMucOptions().everybodyHasKeys()) {
816						Toast warning = Toast
817								.makeText(getActivity(),
818										R.string.missing_public_keys,
819										Toast.LENGTH_LONG);
820						warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
821						warning.show();
822					}
823					activity.encryptTextMessage(message);
824					messageSent();
825				} else {
826					showNoPGPKeyDialog(true,
827							new DialogInterface.OnClickListener() {
828
829								@Override
830								public void onClick(DialogInterface dialog,
831										int which) {
832									conversation
833											.setNextEncryption(Message.ENCRYPTION_NONE);
834									message.setEncryption(Message.ENCRYPTION_NONE);
835									xmppService.databaseBackend
836											.updateConversation(conversation);
837									xmppService.sendMessage(message);
838									messageSent();
839								}
840							});
841				}
842			}
843		} else {
844			activity.showInstallPgpDialog();
845		}
846	}
847
848	public void showNoPGPKeyDialog(boolean plural,
849			DialogInterface.OnClickListener listener) {
850		AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
851		builder.setIconAttribute(android.R.attr.alertDialogIcon);
852		if (plural) {
853			builder.setTitle(getString(R.string.no_pgp_keys));
854			builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
855		} else {
856			builder.setTitle(getString(R.string.no_pgp_key));
857			builder.setMessage(getText(R.string.contact_has_no_pgp_key));
858		}
859		builder.setNegativeButton(getString(R.string.cancel), null);
860		builder.setPositiveButton(getString(R.string.send_unencrypted),
861				listener);
862		builder.create().show();
863	}
864
865	protected void sendOtrMessage(final Message message) {
866		final ConversationActivity activity = (ConversationActivity) getActivity();
867		final XmppConnectionService xmppService = activity.xmppConnectionService;
868		if (conversation.hasValidOtrSession()) {
869			activity.xmppConnectionService.sendMessage(message);
870			messageSent();
871		} else {
872			activity.selectPresence(message.getConversation(),
873					new OnPresenceSelected() {
874
875						@Override
876						public void onPresenceSelected() {
877							message.setPresence(conversation.getNextPresence());
878							xmppService.sendMessage(message);
879							messageSent();
880						}
881					});
882		}
883	}
884
885	public void setText(String text) {
886		this.pastedText = text;
887	}
888
889	public void clearInputField() {
890		this.mEditMessage.setText("");
891	}
892}