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