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