ConversationFragment.java

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