1package eu.siacs.conversations.ui;
2
3import android.annotation.SuppressLint;
4import android.support.v7.app.AlertDialog;
5import android.app.FragmentTransaction;
6import android.app.PendingIntent;
7import android.content.ActivityNotFoundException;
8import android.content.ClipData;
9import android.content.DialogInterface;
10import android.content.DialogInterface.OnClickListener;
11import android.content.Intent;
12import android.content.IntentSender.SendIntentException;
13import android.content.pm.PackageManager;
14import android.net.Uri;
15import android.os.Build;
16import android.os.Bundle;
17import android.os.Handler;
18import android.provider.MediaStore;
19import android.provider.Settings;
20import android.support.v4.widget.SlidingPaneLayout;
21import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
22import android.support.v7.app.ActionBar;
23import android.util.Log;
24import android.util.Pair;
25import android.view.Gravity;
26import android.view.KeyEvent;
27import android.view.Menu;
28import android.view.MenuItem;
29import android.view.Surface;
30import android.view.View;
31import android.widget.AdapterView;
32import android.widget.AdapterView.OnItemClickListener;
33import android.widget.ArrayAdapter;
34import android.widget.CheckBox;
35import android.widget.PopupMenu;
36import android.widget.PopupMenu.OnMenuItemClickListener;
37import android.widget.Toast;
38
39import net.java.otr4j.session.SessionStatus;
40
41import org.openintents.openpgp.util.OpenPgpApi;
42
43import java.util.ArrayList;
44import java.util.Iterator;
45import java.util.List;
46import java.util.concurrent.atomic.AtomicBoolean;
47
48import de.timroes.android.listview.EnhancedListView;
49import eu.siacs.conversations.Config;
50import eu.siacs.conversations.R;
51import eu.siacs.conversations.crypto.axolotl.AxolotlService;
52import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
53import eu.siacs.conversations.entities.Account;
54import eu.siacs.conversations.entities.Blockable;
55import eu.siacs.conversations.entities.Contact;
56import eu.siacs.conversations.entities.Conversation;
57import eu.siacs.conversations.entities.Message;
58import eu.siacs.conversations.entities.Transferable;
59import eu.siacs.conversations.persistance.FileBackend;
60import eu.siacs.conversations.services.XmppConnectionService;
61import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
62import eu.siacs.conversations.services.XmppConnectionService.OnConversationUpdate;
63import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
64import eu.siacs.conversations.ui.adapter.ConversationAdapter;
65import eu.siacs.conversations.ui.service.EmojiService;
66import eu.siacs.conversations.utils.ExceptionHelper;
67import eu.siacs.conversations.utils.UIHelper;
68import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
69import eu.siacs.conversations.xmpp.XmppConnection;
70import eu.siacs.conversations.xmpp.jid.InvalidJidException;
71import eu.siacs.conversations.xmpp.jid.Jid;
72
73public class ConversationActivity extends XmppActivity
74 implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
75
76 public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action";
77
78 public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
79 public static final String CONVERSATION = "conversationUuid";
80 public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
81 public static final String TEXT = "text";
82 public static final String NICK = "nick";
83 public static final String PRIVATE_MESSAGE = "pm";
84
85 public static final int REQUEST_SEND_MESSAGE = 0x0201;
86 public static final int REQUEST_DECRYPT_PGP = 0x0202;
87 public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
88 public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208;
89 public static final int REQUEST_TRUST_KEYS_MENU = 0x0209;
90 public static final int REQUEST_START_DOWNLOAD = 0x0210;
91 public static final int REQUEST_ADD_EDITOR_CONTENT = 0x0211;
92 public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
93 public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
94 public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
95 public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
96 public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
97 public static final int ATTACHMENT_CHOICE_INVALID = 0x0306;
98 public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307;
99 private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
100 private static final String STATE_PANEL_OPEN = "state_panel_open";
101 private static final String STATE_PENDING_URI = "state_pending_uri";
102 private static final String STATE_FIRST_VISIBLE = "first_visible";
103 private static final String STATE_OFFSET_FROM_TOP = "offset_from_top";
104
105 private String mOpenConversation = null;
106 private boolean mPanelOpen = true;
107 private AtomicBoolean mShouldPanelBeOpen = new AtomicBoolean(false);
108 private Pair<Integer,Integer> mScrollPosition = null;
109 final private List<Uri> mPendingImageUris = new ArrayList<>();
110 final private List<Uri> mPendingFileUris = new ArrayList<>();
111 private Uri mPendingGeoUri = null;
112 private boolean forbidProcessingPendings = false;
113 private Message mPendingDownloadableMessage = null;
114
115 private boolean conversationWasSelectedByKeyboard = false;
116
117 private boolean showSoundRecorderAttachment = false;
118 private boolean showLocationAttachment = false;
119
120 private View mContentView;
121
122 private List<Conversation> conversationList = new ArrayList<>();
123 private Conversation swipedConversation = null;
124 private Conversation mSelectedConversation = null;
125 private EnhancedListView listView;
126 private ConversationFragment mConversationFragment;
127
128 private ArrayAdapter<Conversation> listAdapter;
129
130 private boolean mActivityPaused = false;
131 private AtomicBoolean mRedirected = new AtomicBoolean(false);
132 private Pair<Integer, Intent> mPostponedActivityResult;
133 private boolean mUnprocessedNewIntent = false;
134 public Uri mPendingEditorContent = null;
135
136 public Conversation getSelectedConversation() {
137 return this.mSelectedConversation;
138 }
139
140 public void setSelectedConversation(Conversation conversation) {
141 this.mSelectedConversation = conversation;
142 }
143
144 public void showConversationsOverview() {
145 if (mConversationFragment != null) {
146 mConversationFragment.stopScrolling();
147 }
148 if (mContentView instanceof SlidingPaneLayout) {
149 SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
150 mShouldPanelBeOpen.set(true);
151 mSlidingPaneLayout.openPane();
152 }
153 }
154
155 @Override
156 protected String getShareableUri() {
157 Conversation conversation = getSelectedConversation();
158 if (conversation != null) {
159 return conversation.getAccount().getShareableUri();
160 } else {
161 return "";
162 }
163 }
164
165 public void hideConversationsOverview() {
166 if (mContentView instanceof SlidingPaneLayout) {
167 SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
168 mShouldPanelBeOpen.set(false);
169 mSlidingPaneLayout.closePane();
170 }
171 }
172
173 public boolean isConversationsOverviewHideable() {
174 return mContentView instanceof SlidingPaneLayout;
175 }
176
177 public boolean isConversationsOverviewVisable() {
178 if (mContentView instanceof SlidingPaneLayout) {
179 return mShouldPanelBeOpen.get();
180 } else {
181 return true;
182 }
183 }
184
185 @Override
186 protected void onCreate(final Bundle savedInstanceState) {
187 super.onCreate(savedInstanceState);
188 new EmojiService(this).init();
189 if (savedInstanceState != null) {
190 mOpenConversation = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
191 mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
192 int pos = savedInstanceState.getInt(STATE_FIRST_VISIBLE, -1);
193 int offset = savedInstanceState.getInt(STATE_OFFSET_FROM_TOP, 1);
194 if (pos >= 0 && offset <= 0) {
195 Log.d(Config.LOGTAG,"retrieved scroll position from instanceState "+pos+":"+offset);
196 mScrollPosition = new Pair<>(pos,offset);
197 } else {
198 mScrollPosition = null;
199 }
200 String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
201 if (pending != null) {
202 Log.d(Config.LOGTAG,"ConversationsActivity.onCreate() - restoring pending image uri");
203 mPendingImageUris.clear();
204 mPendingImageUris.add(Uri.parse(pending));
205 }
206 }
207
208 setContentView(R.layout.fragment_conversations_overview);
209
210 this.mConversationFragment = new ConversationFragment();
211 FragmentTransaction transaction = getFragmentManager().beginTransaction();
212 transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
213 transaction.commit();
214
215 this.listView = findViewById(R.id.list);
216 this.listAdapter = new ConversationAdapter(this, conversationList);
217 this.listView.setAdapter(this.listAdapter);
218 this.listView.setSwipeDirection(EnhancedListView.SwipeDirection.END);
219
220 final ActionBar actionBar = getSupportActionBar();
221 if (actionBar != null) {
222 actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
223 }
224
225 listView.setOnItemClickListener(new OnItemClickListener() {
226
227 @Override
228 public void onItemClick(AdapterView<?> arg0, View clickedView,
229 int position, long arg3) {
230 if (getSelectedConversation() != conversationList.get(position)) {
231 ConversationActivity.this.mConversationFragment.stopScrolling();
232 setSelectedConversation(conversationList.get(position));
233 ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
234 conversationWasSelectedByKeyboard = false;
235 }
236 hideConversationsOverview();
237 openConversation();
238 }
239 });
240
241 listView.setDismissCallback(new EnhancedListView.OnDismissCallback() {
242
243 @Override
244 public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) {
245
246 final int index = listView.getFirstVisiblePosition();
247 View v = listView.getChildAt(0);
248 final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
249
250 try {
251 swipedConversation = listAdapter.getItem(position);
252 } catch (IndexOutOfBoundsException e) {
253 return null;
254 }
255 listAdapter.remove(swipedConversation);
256 xmppConnectionService.markRead(swipedConversation);
257
258 final boolean formerlySelected = (getSelectedConversation() == swipedConversation);
259 if (position == 0 && listAdapter.getCount() == 0) {
260 endConversation(swipedConversation, false, true);
261 return null;
262 } else if (formerlySelected) {
263 setSelectedConversation(listAdapter.getItem(0));
264 ConversationActivity.this.mConversationFragment
265 .reInit(getSelectedConversation());
266 }
267
268 return new EnhancedListView.Undoable() {
269
270 @Override
271 public void undo() {
272 listAdapter.insert(swipedConversation, position);
273 if (formerlySelected) {
274 setSelectedConversation(swipedConversation);
275 ConversationActivity.this.mConversationFragment
276 .reInit(getSelectedConversation());
277 }
278 swipedConversation = null;
279 listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top);
280 }
281
282 @Override
283 public void discard() {
284 if (!swipedConversation.isRead()
285 && swipedConversation.getMode() == Conversation.MODE_SINGLE) {
286 swipedConversation = null;
287 return;
288 }
289 endConversation(swipedConversation, false, false);
290 swipedConversation = null;
291 }
292
293 @Override
294 public String getTitle() {
295 if (swipedConversation.getMode() == Conversation.MODE_MULTI) {
296 return getResources().getString(R.string.title_undo_swipe_out_muc);
297 } else {
298 return getResources().getString(R.string.title_undo_swipe_out_conversation);
299 }
300 }
301 };
302 }
303 });
304 listView.enableSwipeToDismiss();
305 listView.setSwipingLayout(R.id.swipeable_item);
306 listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
307 listView.setUndoHideDelay(5000);
308 listView.setRequireTouchBeforeDismiss(false);
309
310 mContentView = findViewById(R.id.content_view_spl);
311 if (mContentView == null) {
312 mContentView = findViewById(R.id.content_view_ll);
313 }
314 if (mContentView instanceof SlidingPaneLayout) {
315 SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
316 mSlidingPaneLayout.setShadowResource(R.drawable.es_slidingpane_shadow);
317 mSlidingPaneLayout.setSliderFadeColor(0);
318 mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
319
320 @Override
321 public void onPanelOpened(View arg0) {
322 mShouldPanelBeOpen.set(true);
323 updateActionBarTitle();
324 invalidateOptionsMenu();
325 hideKeyboard();
326 if (xmppConnectionServiceBound) {
327 xmppConnectionService.getNotificationService().setOpenConversation(null);
328 }
329 closeContextMenu();
330 }
331
332 @Override
333 public void onPanelClosed(View arg0) {
334 mShouldPanelBeOpen.set(false);
335 listView.discardUndo();
336 openConversation();
337 }
338
339 @Override
340 public void onPanelSlide(View arg0, float arg1) {
341 // TODO Auto-generated method stub
342
343 }
344 });
345 }
346 }
347
348 @Override
349 public void switchToConversation(Conversation conversation) {
350 setSelectedConversation(conversation);
351 runOnUiThread(() -> {
352 ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
353 openConversation();
354 });
355 }
356
357 private void updateActionBarTitle() {
358 updateActionBarTitle(isConversationsOverviewHideable() && !isConversationsOverviewVisable());
359 }
360
361 private void updateActionBarTitle(boolean titleShouldBeName) {
362 final ActionBar ab = getSupportActionBar();
363 final Conversation conversation = getSelectedConversation();
364 if (ab != null) {
365 if (titleShouldBeName && conversation != null) {
366 if ((ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != ActionBar.DISPLAY_HOME_AS_UP) {
367 ab.setDisplayOptions(ActionBar.DISPLAY_HOME_AS_UP | ActionBar.DISPLAY_SHOW_TITLE);
368 }
369 if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) {
370 ab.setTitle(conversation.getName());
371 } else {
372 ab.setTitle(conversation.getJid().toBareJid().toString());
373 }
374 } else {
375 if ((ab.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) == ActionBar.DISPLAY_HOME_AS_UP) {
376 ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE);
377 }
378 ab.setTitle(R.string.app_name);
379 }
380 }
381 }
382
383 private void openConversation() {
384 this.updateActionBarTitle();
385 this.invalidateOptionsMenu();
386 if (xmppConnectionServiceBound) {
387 final Conversation conversation = getSelectedConversation();
388 xmppConnectionService.getNotificationService().setOpenConversation(conversation);
389 sendReadMarkerIfNecessary(conversation);
390 }
391 listAdapter.notifyDataSetChanged();
392 }
393
394 public void sendReadMarkerIfNecessary(final Conversation conversation) {
395 if (!mActivityPaused && !mUnprocessedNewIntent && conversation != null) {
396 xmppConnectionService.sendReadMarker(conversation);
397 }
398 }
399
400 @Override
401 public boolean onCreateOptionsMenu(Menu menu) {
402 getMenuInflater().inflate(R.menu.conversations, menu);
403 final MenuItem menuSecure = menu.findItem(R.id.action_security);
404 final MenuItem menuArchive = menu.findItem(R.id.action_archive);
405 final MenuItem menuMucDetails = menu.findItem(R.id.action_muc_details);
406 final MenuItem menuContactDetails = menu.findItem(R.id.action_contact_details);
407 final MenuItem menuAttach = menu.findItem(R.id.action_attach_file);
408 final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history);
409 final MenuItem menuAdd = menu.findItem(R.id.action_add);
410 final MenuItem menuInviteContact = menu.findItem(R.id.action_invite);
411 final MenuItem menuMute = menu.findItem(R.id.action_mute);
412 final MenuItem menuUnmute = menu.findItem(R.id.action_unmute);
413 final MenuItem menuAttachSoundRecorder = menu.findItem(R.id.attach_record_voice);
414 final MenuItem menuAttachLocation = menu.findItem(R.id.attach_location);
415
416 if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) {
417 menuArchive.setVisible(false);
418 menuMucDetails.setVisible(false);
419 menuContactDetails.setVisible(false);
420 menuSecure.setVisible(false);
421 menuInviteContact.setVisible(false);
422 menuAttach.setVisible(false);
423 menuClearHistory.setVisible(false);
424 menuMute.setVisible(false);
425 menuUnmute.setVisible(false);
426 } else {
427 menuAdd.setVisible(!isConversationsOverviewHideable());
428 if (this.getSelectedConversation() != null) {
429 if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) {
430 menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
431 }
432 if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
433 menuContactDetails.setVisible(false);
434 menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating());
435 menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
436 menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice
437 } else {
438 menuContactDetails.setVisible(!this.getSelectedConversation().withSelf());
439 menuMucDetails.setVisible(false);
440 menuSecure.setVisible(Config.multipleEncryptionChoices());
441 menuInviteContact.setVisible(xmppConnectionService != null && xmppConnectionService.findConferenceServer(getSelectedConversation().getAccount()) != null);
442 }
443 if (this.getSelectedConversation().isMuted()) {
444 menuMute.setVisible(false);
445 } else {
446 menuUnmute.setVisible(false);
447 }
448 menuAttachLocation.setVisible(showLocationAttachment);
449 menuAttachSoundRecorder.setVisible(showSoundRecorderAttachment);
450 configureEncryptionMenu(getSelectedConversation(),menu);
451 }
452 }
453 return super.onCreateOptionsMenu(menu);
454 }
455
456 private static void configureEncryptionMenu(Conversation conversation, Menu menu) {
457 MenuItem otr = menu.findItem(R.id.encryption_choice_otr);
458 MenuItem none = menu.findItem(R.id.encryption_choice_none);
459 MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp);
460 MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl);
461 pgp.setVisible(Config.supportOpenPgp());
462 none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI);
463 otr.setVisible(Config.supportOtr());
464 axolotl.setVisible(Config.supportOmemo());
465 if (conversation.getMode() == Conversation.MODE_MULTI) {
466 otr.setVisible(false);
467 }
468 if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) {
469 axolotl.setEnabled(false);
470 }
471 switch (conversation.getNextEncryption()) {
472 case Message.ENCRYPTION_NONE:
473 none.setChecked(true);
474 break;
475 case Message.ENCRYPTION_OTR:
476 otr.setChecked(true);
477 break;
478 case Message.ENCRYPTION_PGP:
479 pgp.setChecked(true);
480 break;
481 case Message.ENCRYPTION_AXOLOTL:
482 axolotl.setChecked(true);
483 break;
484 default:
485 none.setChecked(true);
486 break;
487 }
488 }
489
490 protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
491 final Conversation conversation = getSelectedConversation();
492 final Account account = conversation.getAccount();
493 final OnPresenceSelected callback = () -> {
494 Intent intent = new Intent();
495 boolean chooser = false;
496 String fallbackPackageId = null;
497 switch (attachmentChoice) {
498 case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
499 intent.setAction(Intent.ACTION_GET_CONTENT);
500 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
501 intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
502 }
503 intent.setType("image/*");
504 chooser = true;
505 break;
506 case ATTACHMENT_CHOICE_RECORD_VIDEO:
507 intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
508 break;
509 case ATTACHMENT_CHOICE_TAKE_PHOTO:
510 Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
511 mPendingImageUris.clear();
512 mPendingImageUris.add(uri);
513 intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
514 intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
515 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
516 intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
517 break;
518 case ATTACHMENT_CHOICE_CHOOSE_FILE:
519 chooser = true;
520 intent.setType("*/*");
521 intent.addCategory(Intent.CATEGORY_OPENABLE);
522 intent.setAction(Intent.ACTION_GET_CONTENT);
523 break;
524 case ATTACHMENT_CHOICE_RECORD_VOICE:
525 intent.setAction(MediaStore.Audio.Media.RECORD_SOUND_ACTION);
526 fallbackPackageId = "eu.siacs.conversations.voicerecorder";
527 break;
528 case ATTACHMENT_CHOICE_LOCATION:
529 intent.setAction("eu.siacs.conversations.location.request");
530 fallbackPackageId = "eu.siacs.conversations.sharelocation";
531 break;
532 }
533 if (intent.resolveActivity(getPackageManager()) != null) {
534 if (chooser) {
535 startActivityForResult(
536 Intent.createChooser(intent, getString(R.string.perform_action_with)),
537 attachmentChoice);
538 } else {
539 startActivityForResult(intent, attachmentChoice);
540 }
541 } else if (fallbackPackageId != null) {
542 startActivity(getInstallApkIntent(fallbackPackageId));
543 }
544 };
545 if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) {
546 conversation.setNextCounterpart(null);
547 callback.onPresenceSelected();
548 } else {
549 selectPresence(conversation, callback);
550 }
551 }
552
553 private Intent getInstallApkIntent(final String packageId) {
554 Intent intent = new Intent(Intent.ACTION_VIEW);
555 intent.setData(Uri.parse("market://details?id=" + packageId));
556 if (intent.resolveActivity(getPackageManager()) != null) {
557 return intent;
558 } else {
559 intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId));
560 return intent;
561 }
562 }
563
564 public void attachFile(final int attachmentChoice) {
565 if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) {
566 if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(attachmentChoice)) {
567 return;
568 }
569 }
570 final ConversationFragment.SendButtonAction action;
571 switch (attachmentChoice) {
572 case ATTACHMENT_CHOICE_LOCATION:
573 action = ConversationFragment.SendButtonAction.SEND_LOCATION;
574 break;
575 case ATTACHMENT_CHOICE_RECORD_VOICE:
576 action = ConversationFragment.SendButtonAction.RECORD_VOICE;
577 break;
578 case ATTACHMENT_CHOICE_RECORD_VIDEO:
579 action = ConversationFragment.SendButtonAction.RECORD_VIDEO;
580 break;
581 case ATTACHMENT_CHOICE_TAKE_PHOTO:
582 action = ConversationFragment.SendButtonAction.TAKE_PHOTO;
583 break;
584 case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
585 action = ConversationFragment.SendButtonAction.CHOOSE_PICTURE;
586 break;
587 default:
588 action = null;
589 break;
590 }
591 if (action != null) {
592 getPreferences().edit().putString(RECENTLY_USED_QUICK_ACTION,action.toString()).apply();
593 }
594 final Conversation conversation = getSelectedConversation();
595 final int encryption = conversation.getNextEncryption();
596 final int mode = conversation.getMode();
597 if (encryption == Message.ENCRYPTION_PGP) {
598 if (hasPgp()) {
599 if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) {
600 xmppConnectionService.getPgpEngine().hasKey(
601 conversation.getContact(),
602 new UiCallback<Contact>() {
603
604 @Override
605 public void userInputRequried(PendingIntent pi, Contact contact) {
606 ConversationActivity.this.runIntent(pi, attachmentChoice);
607 }
608
609 @Override
610 public void success(Contact contact) {
611 selectPresenceToAttachFile(attachmentChoice, encryption);
612 }
613
614 @Override
615 public void error(int error, Contact contact) {
616 replaceToast(getString(error));
617 }
618 });
619 } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) {
620 if (!conversation.getMucOptions().everybodyHasKeys()) {
621 Toast warning = Toast
622 .makeText(this,
623 R.string.missing_public_keys,
624 Toast.LENGTH_LONG);
625 warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
626 warning.show();
627 }
628 selectPresenceToAttachFile(attachmentChoice, encryption);
629 } else {
630 final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
631 .findFragmentByTag("conversation");
632 if (fragment != null) {
633 fragment.showNoPGPKeyDialog(false,
634 new OnClickListener() {
635
636 @Override
637 public void onClick(DialogInterface dialog,
638 int which) {
639 conversation.setNextEncryption(Message.ENCRYPTION_NONE);
640 xmppConnectionService.updateConversation(conversation);
641 selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE);
642 }
643 });
644 }
645 }
646 } else {
647 showInstallPgpDialog();
648 }
649 } else {
650 if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) {
651 selectPresenceToAttachFile(attachmentChoice, encryption);
652 }
653 }
654 }
655
656 @Override
657 public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
658 if (grantResults.length > 0)
659 if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
660 if (requestCode == REQUEST_START_DOWNLOAD) {
661 if (this.mPendingDownloadableMessage != null) {
662 startDownloadable(this.mPendingDownloadableMessage);
663 }
664 } else if (requestCode == REQUEST_ADD_EDITOR_CONTENT) {
665 if (this.mPendingEditorContent != null) {
666 attachImageToConversation(this.mPendingEditorContent);
667 }
668 } else {
669 attachFile(requestCode);
670 }
671 } else {
672 Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
673 }
674 }
675
676 public void startDownloadable(Message message) {
677 if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) {
678 this.mPendingDownloadableMessage = message;
679 return;
680 }
681 Transferable transferable = message.getTransferable();
682 if (transferable != null) {
683 if (!transferable.start()) {
684 Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
685 }
686 } else if (message.treatAsDownloadable()) {
687 xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true);
688 }
689 }
690
691 @Override
692 public boolean onOptionsItemSelected(final MenuItem item) {
693 if (item.getItemId() == android.R.id.home) {
694 showConversationsOverview();
695 return true;
696 } else if (item.getItemId() == R.id.action_add) {
697 startActivity(new Intent(this, StartConversationActivity.class));
698 return true;
699 } else if (getSelectedConversation() != null) {
700 switch (item.getItemId()) {
701 case R.id.encryption_choice_axolotl:
702 case R.id.encryption_choice_otr:
703 case R.id.encryption_choice_pgp:
704 case R.id.encryption_choice_none:
705 handleEncryptionSelection(item);
706 break;
707 case R.id.attach_choose_picture:
708 attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
709 break;
710 case R.id.attach_take_picture:
711 attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
712 break;
713 case R.id.attach_record_video:
714 attachFile(ATTACHMENT_CHOICE_RECORD_VIDEO);
715 break;
716 case R.id.attach_choose_file:
717 attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE);
718 break;
719 case R.id.attach_record_voice:
720 attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
721 break;
722 case R.id.attach_location:
723 attachFile(ATTACHMENT_CHOICE_LOCATION);
724 break;
725 case R.id.action_archive:
726 this.endConversation(getSelectedConversation());
727 break;
728 case R.id.action_contact_details:
729 switchToContactDetails(getSelectedConversation().getContact());
730 break;
731 case R.id.action_muc_details:
732 Intent intent = new Intent(this,
733 ConferenceDetailsActivity.class);
734 intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
735 intent.putExtra("uuid", getSelectedConversation().getUuid());
736 startActivity(intent);
737 break;
738 case R.id.action_invite:
739 inviteToConversation(getSelectedConversation());
740 break;
741 case R.id.action_clear_history:
742 clearHistoryDialog(getSelectedConversation());
743 break;
744 case R.id.action_mute:
745 muteConversationDialog(getSelectedConversation());
746 break;
747 case R.id.action_unmute:
748 unmuteConversation(getSelectedConversation());
749 break;
750 case R.id.action_block:
751 BlockContactDialog.show(this, getSelectedConversation());
752 break;
753 case R.id.action_unblock:
754 BlockContactDialog.show(this, getSelectedConversation());
755 break;
756 default:
757 break;
758 }
759 return super.onOptionsItemSelected(item);
760 } else {
761 return super.onOptionsItemSelected(item);
762 }
763 }
764
765 public void endConversation(Conversation conversation) {
766 endConversation(conversation, true, true);
767 }
768
769 public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) {
770 if (showOverview) {
771 showConversationsOverview();
772 }
773 xmppConnectionService.archiveConversation(conversation);
774 if (reinit) {
775 if (conversationList.size() > 0) {
776 setSelectedConversation(conversationList.get(0));
777 this.mConversationFragment.reInit(getSelectedConversation());
778 } else {
779 setSelectedConversation(null);
780 if (mRedirected.compareAndSet(false, true)) {
781 Intent intent = new Intent(this, StartConversationActivity.class);
782 intent.putExtra("init", true);
783 startActivity(intent);
784 finish();
785 }
786 }
787 }
788 }
789
790 @SuppressLint("InflateParams")
791 protected void clearHistoryDialog(final Conversation conversation) {
792 AlertDialog.Builder builder = new AlertDialog.Builder(this);
793 builder.setTitle(getString(R.string.clear_conversation_history));
794 final View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
795 final CheckBox endConversationCheckBox = dialogView.findViewById(R.id.end_conversation_checkbox);
796 builder.setView(dialogView);
797 builder.setNegativeButton(getString(R.string.cancel), null);
798 builder.setPositiveButton(getString(R.string.delete_messages),
799 (dialog, which) -> {
800 ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation);
801 if (endConversationCheckBox.isChecked()) {
802 endConversation(conversation);
803 } else {
804 updateConversationList();
805 ConversationActivity.this.mConversationFragment.updateMessages();
806 }
807 });
808 builder.create().show();
809 }
810
811 public void verifyOtrSessionDialog(final Conversation conversation, View view) {
812 if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
813 Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show();
814 return;
815 }
816 if (view == null) {
817 return;
818 }
819 PopupMenu popup = new PopupMenu(this, view);
820 popup.inflate(R.menu.verification_choices);
821 popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
822 @Override
823 public boolean onMenuItemClick(MenuItem menuItem) {
824 Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class);
825 intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
826 intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
827 intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
828 switch (menuItem.getItemId()) {
829 case R.id.scan_fingerprint:
830 intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT);
831 break;
832 case R.id.ask_question:
833 intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION);
834 break;
835 case R.id.manual_verification:
836 intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION);
837 break;
838 }
839 startActivity(intent);
840 return true;
841 }
842 });
843 popup.show();
844 }
845
846 private void handleEncryptionSelection(MenuItem item) {
847 Conversation conversation = getSelectedConversation();
848 if (conversation == null) {
849 return;
850 }
851 final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation");
852 switch (item.getItemId()) {
853 case R.id.encryption_choice_none:
854 conversation.setNextEncryption(Message.ENCRYPTION_NONE);
855 item.setChecked(true);
856 break;
857 case R.id.encryption_choice_otr:
858 conversation.setNextEncryption(Message.ENCRYPTION_OTR);
859 item.setChecked(true);
860 break;
861 case R.id.encryption_choice_pgp:
862 if (hasPgp()) {
863 if (conversation.getAccount().getPgpSignature() != null) {
864 conversation.setNextEncryption(Message.ENCRYPTION_PGP);
865 item.setChecked(true);
866 } else {
867 announcePgp(conversation.getAccount(), conversation,null, onOpenPGPKeyPublished);
868 }
869 } else {
870 showInstallPgpDialog();
871 }
872 break;
873 case R.id.encryption_choice_axolotl:
874 Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
875 + "Enabled axolotl for Contact " + conversation.getContact().getJid());
876 conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
877 item.setChecked(true);
878 break;
879 default:
880 conversation.setNextEncryption(Message.ENCRYPTION_NONE);
881 break;
882 }
883 xmppConnectionService.updateConversation(conversation);
884 fragment.updateChatMsgHint();
885 invalidateOptionsMenu();
886 refreshUi();
887 }
888
889 protected void muteConversationDialog(final Conversation conversation) {
890 AlertDialog.Builder builder = new AlertDialog.Builder(this);
891 builder.setTitle(R.string.disable_notifications);
892 final int[] durations = getResources().getIntArray(R.array.mute_options_durations);
893 builder.setItems(R.array.mute_options_descriptions,
894 new OnClickListener() {
895
896 @Override
897 public void onClick(final DialogInterface dialog, final int which) {
898 final long till;
899 if (durations[which] == -1) {
900 till = Long.MAX_VALUE;
901 } else {
902 till = System.currentTimeMillis() + (durations[which] * 1000);
903 }
904 conversation.setMutedTill(till);
905 ConversationActivity.this.xmppConnectionService.updateConversation(conversation);
906 updateConversationList();
907 ConversationActivity.this.mConversationFragment.updateMessages();
908 invalidateOptionsMenu();
909 }
910 });
911 builder.create().show();
912 }
913
914 public void unmuteConversation(final Conversation conversation) {
915 conversation.setMutedTill(0);
916 this.xmppConnectionService.updateConversation(conversation);
917 updateConversationList();
918 ConversationActivity.this.mConversationFragment.updateMessages();
919 invalidateOptionsMenu();
920 }
921
922 @Override
923 public void onBackPressed() {
924 if (!isConversationsOverviewVisable()) {
925 showConversationsOverview();
926 } else {
927 super.onBackPressed();
928 }
929 }
930
931 @Override
932 public boolean onKeyUp(int key, KeyEvent event) {
933 int rotation = getWindowManager().getDefaultDisplay().getRotation();
934 final int upKey;
935 final int downKey;
936 switch (rotation) {
937 case Surface.ROTATION_90:
938 upKey = KeyEvent.KEYCODE_DPAD_LEFT;
939 downKey = KeyEvent.KEYCODE_DPAD_RIGHT;
940 break;
941 case Surface.ROTATION_180:
942 upKey = KeyEvent.KEYCODE_DPAD_DOWN;
943 downKey = KeyEvent.KEYCODE_DPAD_UP;
944 break;
945 case Surface.ROTATION_270:
946 upKey = KeyEvent.KEYCODE_DPAD_RIGHT;
947 downKey = KeyEvent.KEYCODE_DPAD_LEFT;
948 break;
949 case Surface.ROTATION_0:
950 default:
951 upKey = KeyEvent.KEYCODE_DPAD_UP;
952 downKey = KeyEvent.KEYCODE_DPAD_DOWN;
953 }
954 final boolean modifier = event.isCtrlPressed() || (event.getMetaState() & KeyEvent.META_ALT_LEFT_ON) != 0;
955 if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) {
956 toggleConversationsOverview();
957 return true;
958 } else if (modifier && key == KeyEvent.KEYCODE_SPACE) {
959 startActivity(new Intent(this, StartConversationActivity.class));
960 return true;
961 } else if (modifier && key == downKey) {
962 if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
963 showConversationsOverview();
964 ;
965 }
966 return selectDownConversation();
967 } else if (modifier && key == upKey) {
968 if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
969 showConversationsOverview();
970 }
971 return selectUpConversation();
972 } else if (modifier && key == KeyEvent.KEYCODE_1) {
973 return openConversationByIndex(0);
974 } else if (modifier && key == KeyEvent.KEYCODE_2) {
975 return openConversationByIndex(1);
976 } else if (modifier && key == KeyEvent.KEYCODE_3) {
977 return openConversationByIndex(2);
978 } else if (modifier && key == KeyEvent.KEYCODE_4) {
979 return openConversationByIndex(3);
980 } else if (modifier && key == KeyEvent.KEYCODE_5) {
981 return openConversationByIndex(4);
982 } else if (modifier && key == KeyEvent.KEYCODE_6) {
983 return openConversationByIndex(5);
984 } else if (modifier && key == KeyEvent.KEYCODE_7) {
985 return openConversationByIndex(6);
986 } else if (modifier && key == KeyEvent.KEYCODE_8) {
987 return openConversationByIndex(7);
988 } else if (modifier && key == KeyEvent.KEYCODE_9) {
989 return openConversationByIndex(8);
990 } else if (modifier && key == KeyEvent.KEYCODE_0) {
991 return openConversationByIndex(9);
992 } else {
993 return super.onKeyUp(key, event);
994 }
995 }
996
997 private void toggleConversationsOverview() {
998 if (isConversationsOverviewVisable()) {
999 hideConversationsOverview();
1000 if (mConversationFragment != null) {
1001 mConversationFragment.setFocusOnInputField();
1002 }
1003 } else {
1004 showConversationsOverview();
1005 }
1006 }
1007
1008 private boolean selectUpConversation() {
1009 if (this.mSelectedConversation != null) {
1010 int index = this.conversationList.indexOf(this.mSelectedConversation);
1011 if (index > 0) {
1012 return openConversationByIndex(index - 1);
1013 }
1014 }
1015 return false;
1016 }
1017
1018 private boolean selectDownConversation() {
1019 if (this.mSelectedConversation != null) {
1020 int index = this.conversationList.indexOf(this.mSelectedConversation);
1021 if (index != -1 && index < this.conversationList.size() - 1) {
1022 return openConversationByIndex(index + 1);
1023 }
1024 }
1025 return false;
1026 }
1027
1028 private boolean openConversationByIndex(int index) {
1029 try {
1030 this.conversationWasSelectedByKeyboard = true;
1031 this.mConversationFragment.stopScrolling();
1032 setSelectedConversation(this.conversationList.get(index));
1033 this.mConversationFragment.reInit(getSelectedConversation());
1034 if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) {
1035 this.listView.setSelection(index);
1036 }
1037 openConversation();
1038 return true;
1039 } catch (IndexOutOfBoundsException e) {
1040 return false;
1041 }
1042 }
1043
1044 @Override
1045 protected void onNewIntent(final Intent intent) {
1046 if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) {
1047 mOpenConversation = null;
1048 mUnprocessedNewIntent = true;
1049 if (xmppConnectionServiceBound) {
1050 handleViewConversationIntent(intent);
1051 intent.setAction(Intent.ACTION_MAIN);
1052 } else {
1053 setIntent(intent);
1054 }
1055 }
1056 }
1057
1058 @Override
1059 public void onStart() {
1060 super.onStart();
1061 this.mRedirected.set(false);
1062 if (this.xmppConnectionServiceBound) {
1063 this.onBackendConnected();
1064 }
1065 if (conversationList.size() >= 1) {
1066 this.onConversationUpdate();
1067 }
1068 }
1069
1070 @Override
1071 public void onPause() {
1072 listView.discardUndo();
1073 super.onPause();
1074 this.mActivityPaused = true;
1075 }
1076
1077 @Override
1078 public void onResume() {
1079 super.onResume();
1080 final int theme = findTheme();
1081 final boolean usingEnterKey = usingEnterKey();
1082 if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) {
1083 recreate();
1084 }
1085 this.mActivityPaused = false;
1086 if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) {
1087 sendReadMarkerIfNecessary(getSelectedConversation());
1088 }
1089 new Handler().post(() -> {
1090 showSoundRecorderAttachment = new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) != null;
1091 showLocationAttachment = new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) != null;
1092 invalidateOptionsMenu();
1093 });
1094 }
1095
1096 @Override
1097 public void onSaveInstanceState(final Bundle savedInstanceState) {
1098 Conversation conversation = getSelectedConversation();
1099 if (conversation != null) {
1100 savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid());
1101 Pair<Integer,Integer> scrollPosition = mConversationFragment.getScrollPosition();
1102 if (scrollPosition != null) {
1103 savedInstanceState.putInt(STATE_FIRST_VISIBLE, scrollPosition.first);
1104 savedInstanceState.putInt(STATE_OFFSET_FROM_TOP, scrollPosition.second);
1105 }
1106 } else {
1107 savedInstanceState.remove(STATE_OPEN_CONVERSATION);
1108 }
1109 savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable());
1110 if (this.mPendingImageUris.size() >= 1) {
1111 Log.d(Config.LOGTAG,"ConversationsActivity.onSaveInstanceState() - saving pending image uri");
1112 savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
1113 } else {
1114 savedInstanceState.remove(STATE_PENDING_URI);
1115 }
1116 super.onSaveInstanceState(savedInstanceState);
1117 }
1118
1119 private void clearPending() {
1120 mPendingImageUris.clear();
1121 mPendingFileUris.clear();
1122 mPendingGeoUri = null;
1123 mPostponedActivityResult = null;
1124 }
1125
1126 private void redirectToStartConversationActivity(boolean noAnimation) {
1127 Account pendingAccount = xmppConnectionService.getPendingAccount();
1128 if (pendingAccount == null) {
1129 Intent startConversationActivity = new Intent(this, StartConversationActivity.class);
1130 startConversationActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
1131 if (noAnimation) {
1132 startConversationActivity.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
1133 }
1134 startConversationActivity.putExtra("init", true);
1135 startActivity(startConversationActivity);
1136 if (noAnimation) {
1137 overridePendingTransition(0,0);
1138 }
1139 } else {
1140 switchToAccount(pendingAccount, true);
1141 }
1142 }
1143
1144 @Override
1145 void onBackendConnected() {
1146 this.xmppConnectionService.getNotificationService().setIsInForeground(true);
1147 updateConversationList();
1148
1149 if (mPendingConferenceInvite != null) {
1150 if (mPendingConferenceInvite.execute(this)) {
1151 mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
1152 mToast.show();
1153 }
1154 mPendingConferenceInvite = null;
1155 }
1156
1157 final Intent intent = getIntent();
1158
1159 if (xmppConnectionService.getAccounts().size() == 0) {
1160 if (mRedirected.compareAndSet(false, true)) {
1161 if (Config.X509_VERIFICATION) {
1162 Intent redirectionIntent = new Intent(this, ManageAccountActivity.class);
1163 redirectionIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
1164 startActivity(redirectionIntent);
1165 overridePendingTransition(0,0);
1166 } else if (Config.MAGIC_CREATE_DOMAIN != null) {
1167 WelcomeActivity.launch(this);
1168 } else {
1169 Intent editAccount = new Intent(this, EditAccountActivity.class);
1170 editAccount.putExtra("init",true);
1171 editAccount.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION);
1172 startActivity(editAccount);
1173 overridePendingTransition(0,0);
1174 }
1175 }
1176 } else if (conversationList.size() <= 0) {
1177 if (mRedirected.compareAndSet(false, true)) {
1178 redirectToStartConversationActivity(true);
1179 }
1180 } else if (selectConversationByUuid(mOpenConversation)) {
1181 if (mPanelOpen) {
1182 showConversationsOverview();
1183 } else {
1184 if (isConversationsOverviewHideable()) {
1185 openConversation();
1186 updateActionBarTitle(true);
1187 }
1188 }
1189 if (this.mConversationFragment.reInit(getSelectedConversation())) {
1190 Log.d(Config.LOGTAG,"setting scroll position on fragment");
1191 this.mConversationFragment.setScrollPosition(mScrollPosition);
1192 }
1193 mOpenConversation = null;
1194 } else if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) {
1195 clearPending();
1196 handleViewConversationIntent(intent);
1197 intent.setAction(Intent.ACTION_MAIN);
1198 } else if (getSelectedConversation() == null) {
1199 reInitLatestConversation();
1200 } else {
1201 this.mConversationFragment.messageListAdapter.updatePreferences();
1202 this.mConversationFragment.messagesView.invalidateViews();
1203 this.mConversationFragment.setupIme();
1204 }
1205
1206 if (this.mPostponedActivityResult != null) {
1207 this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
1208 }
1209
1210 final boolean stopping = isStopping();
1211
1212 if (!forbidProcessingPendings) {
1213 for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
1214 Uri foo = i.next();
1215 Log.d(Config.LOGTAG,"ConversationsActivity.onBackendConnected() - attaching image to conversations. stopping="+Boolean.toString(stopping));
1216 attachImageToConversation(getSelectedConversation(), foo);
1217 }
1218
1219 for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
1220 Log.d(Config.LOGTAG,"ConversationsActivity.onBackendConnected() - attaching file to conversations. stopping="+Boolean.toString(stopping));
1221 attachFileToConversation(getSelectedConversation(), i.next());
1222 }
1223
1224 if (mPendingGeoUri != null) {
1225 attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
1226 mPendingGeoUri = null;
1227 }
1228 }
1229 forbidProcessingPendings = false;
1230
1231 if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService) && !mRedirected.get()) {
1232 openBatteryOptimizationDialogIfNeeded();
1233 }
1234 if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) {
1235 xmppConnectionService.getNotificationService().setOpenConversation(null);
1236 } else {
1237 xmppConnectionService.getNotificationService().setOpenConversation(getSelectedConversation());
1238 }
1239 }
1240
1241 private boolean isStopping() {
1242 if (Build.VERSION.SDK_INT >= 17) {
1243 return isFinishing() || isDestroyed();
1244 } else {
1245 return isFinishing();
1246 }
1247 }
1248
1249 private void reInitLatestConversation() {
1250 showConversationsOverview();
1251 clearPending();
1252 setSelectedConversation(conversationList.get(0));
1253 this.mConversationFragment.reInit(getSelectedConversation());
1254 }
1255
1256 private void handleViewConversationIntent(final Intent intent) {
1257 final String uuid = intent.getStringExtra(CONVERSATION);
1258 final String downloadUuid = intent.getStringExtra(EXTRA_DOWNLOAD_UUID);
1259 final String text = intent.getStringExtra(TEXT);
1260 final String nick = intent.getStringExtra(NICK);
1261 final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false);
1262 this.mConversationFragment.stopScrolling();
1263 if (selectConversationByUuid(uuid)) {
1264 this.mConversationFragment.reInit(getSelectedConversation());
1265 if (nick != null) {
1266 if (pm) {
1267 Jid jid = getSelectedConversation().getJid();
1268 try {
1269 Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick);
1270 this.mConversationFragment.privateMessageWith(next);
1271 } catch (final InvalidJidException ignored) {
1272 //do nothing
1273 }
1274 } else {
1275 this.mConversationFragment.highlightInConference(nick);
1276 }
1277 } else {
1278 this.mConversationFragment.appendText(text);
1279 }
1280 hideConversationsOverview();
1281 mUnprocessedNewIntent = false;
1282 openConversation();
1283 if (mContentView instanceof SlidingPaneLayout) {
1284 updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet
1285 }
1286 if (downloadUuid != null) {
1287 final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid);
1288 if (message != null) {
1289 startDownloadable(message);
1290 }
1291 }
1292 } else {
1293 mUnprocessedNewIntent = false;
1294 }
1295 }
1296
1297 private boolean selectConversationByUuid(String uuid) {
1298 if (uuid == null) {
1299 return false;
1300 }
1301 for (Conversation aConversationList : conversationList) {
1302 if (aConversationList.getUuid().equals(uuid)) {
1303 setSelectedConversation(aConversationList);
1304 return true;
1305 }
1306 }
1307 return false;
1308 }
1309
1310 @Override
1311 protected void unregisterListeners() {
1312 super.unregisterListeners();
1313 xmppConnectionService.getNotificationService().setOpenConversation(null);
1314 }
1315
1316 @SuppressLint("NewApi")
1317 private static List<Uri> extractUriFromIntent(final Intent intent) {
1318 List<Uri> uris = new ArrayList<>();
1319 if (intent == null) {
1320 return uris;
1321 }
1322 Uri uri = intent.getData();
1323 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) {
1324 final ClipData clipData = intent.getClipData();
1325 if (clipData != null) {
1326 for (int i = 0; i < clipData.getItemCount(); ++i) {
1327 uris.add(clipData.getItemAt(i).getUri());
1328 }
1329 }
1330 } else {
1331 uris.add(uri);
1332 }
1333 return uris;
1334 }
1335
1336 @Override
1337 protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
1338 super.onActivityResult(requestCode, resultCode, data);
1339 if (resultCode == RESULT_OK) {
1340 if (requestCode == REQUEST_DECRYPT_PGP) {
1341 mConversationFragment.onActivityResult(requestCode, resultCode, data);
1342 } else if (requestCode == REQUEST_CHOOSE_PGP_ID) {
1343 // the user chose OpenPGP for encryption and selected his key in the PGP provider
1344 if (xmppConnectionServiceBound) {
1345 if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
1346 // associate selected PGP keyId with the account
1347 mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
1348 // we need to announce the key as described in XEP-027
1349 announcePgp(mSelectedConversation.getAccount(), null, null, onOpenPGPKeyPublished);
1350 } else {
1351 choosePgpSignId(mSelectedConversation.getAccount());
1352 }
1353 this.mPostponedActivityResult = null;
1354 } else {
1355 this.mPostponedActivityResult = new Pair<>(requestCode, data);
1356 }
1357 } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
1358 if (xmppConnectionServiceBound) {
1359 announcePgp(mSelectedConversation.getAccount(), mSelectedConversation,data, onOpenPGPKeyPublished);
1360 this.mPostponedActivityResult = null;
1361 } else {
1362 this.mPostponedActivityResult = new Pair<>(requestCode, data);
1363 }
1364 } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
1365 mPendingImageUris.clear();
1366 mPendingImageUris.addAll(extractUriFromIntent(data));
1367 if (xmppConnectionServiceBound) {
1368 for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
1369 Log.d(Config.LOGTAG,"ConversationsActivity.onActivityResult() - attaching image to conversations. CHOOSE_IMAGE");
1370 attachImageToConversation(getSelectedConversation(), i.next());
1371 }
1372 }
1373 } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE || requestCode == ATTACHMENT_CHOICE_RECORD_VIDEO) {
1374 final List<Uri> uris = extractUriFromIntent(data);
1375 Log.d(Config.LOGTAG,"uris "+uris.toString());
1376 final Conversation c = getSelectedConversation();
1377 final OnPresenceSelected callback = new OnPresenceSelected() {
1378 @Override
1379 public void onPresenceSelected() {
1380 mPendingFileUris.clear();
1381 mPendingFileUris.addAll(uris);
1382 if (xmppConnectionServiceBound) {
1383 for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
1384 Log.d(Config.LOGTAG,"ConversationsActivity.onActivityResult() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
1385 attachFileToConversation(c, i.next());
1386 }
1387 }
1388 }
1389 };
1390 if (c == null || c.getMode() == Conversation.MODE_MULTI
1391 || FileBackend.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c))
1392 || c.getNextEncryption() == Message.ENCRYPTION_OTR) {
1393 callback.onPresenceSelected();
1394 } else {
1395 selectPresence(c, callback);
1396 }
1397 } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
1398 if (mPendingImageUris.size() == 1) {
1399 Uri uri = FileBackend.getIndexableTakePhotoUri(mPendingImageUris.get(0));
1400 mPendingImageUris.set(0, uri);
1401 if (xmppConnectionServiceBound) {
1402 Log.d(Config.LOGTAG,"ConversationsActivity.onActivityResult() - attaching image to conversations. TAKE_PHOTO");
1403 attachImageToConversation(getSelectedConversation(), uri);
1404 mPendingImageUris.clear();
1405 }
1406 if (!Config.ONLY_INTERNAL_STORAGE) {
1407 Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
1408 intent.setData(uri);
1409 sendBroadcast(intent);
1410 }
1411 } else {
1412 mPendingImageUris.clear();
1413 }
1414 } else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
1415 double latitude = data.getDoubleExtra("latitude", 0);
1416 double longitude = data.getDoubleExtra("longitude", 0);
1417 this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude));
1418 if (xmppConnectionServiceBound) {
1419 attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
1420 this.mPendingGeoUri = null;
1421 }
1422 } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) {
1423 this.forbidProcessingPendings = true;
1424 if (xmppConnectionServiceBound) {
1425 mConversationFragment.onActivityResult(requestCode, resultCode, data);
1426 this.mPostponedActivityResult = null;
1427 } else {
1428 this.mPostponedActivityResult = new Pair<>(requestCode, data);
1429 }
1430
1431 }
1432 } else {
1433 mPendingImageUris.clear();
1434 mPendingFileUris.clear();
1435 if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
1436 mConversationFragment.onActivityResult(requestCode, resultCode, data);
1437 }
1438 if (requestCode == REQUEST_BATTERY_OP) {
1439 setNeverAskForBatteryOptimizationsAgain();
1440 }
1441 }
1442 }
1443
1444 private long getMaxHttpUploadSize(Conversation conversation) {
1445 final XmppConnection connection = conversation.getAccount().getXmppConnection();
1446 return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize();
1447 }
1448
1449 private String getBatteryOptimizationPreferenceKey() {
1450 @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
1451 return "show_battery_optimization"+(device == null ? "" : device);
1452 }
1453
1454 private void setNeverAskForBatteryOptimizationsAgain() {
1455 getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
1456 }
1457
1458 private void openBatteryOptimizationDialogIfNeeded() {
1459 if (hasAccountWithoutPush()
1460 && isOptimizingBattery()
1461 && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
1462 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1463 builder.setTitle(R.string.battery_optimizations_enabled);
1464 builder.setMessage(R.string.battery_optimizations_enabled_dialog);
1465 builder.setPositiveButton(R.string.next, new OnClickListener() {
1466 @Override
1467 public void onClick(DialogInterface dialog, int which) {
1468 Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
1469 Uri uri = Uri.parse("package:" + getPackageName());
1470 intent.setData(uri);
1471 try {
1472 startActivityForResult(intent, REQUEST_BATTERY_OP);
1473 } catch (ActivityNotFoundException e) {
1474 Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
1475 }
1476 }
1477 });
1478 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
1479 builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
1480 @Override
1481 public void onDismiss(DialogInterface dialog) {
1482 setNeverAskForBatteryOptimizationsAgain();
1483 }
1484 });
1485 }
1486 AlertDialog dialog = builder.create();
1487 dialog.setCanceledOnTouchOutside(false);
1488 dialog.show();
1489 }
1490 }
1491
1492 private boolean hasAccountWithoutPush() {
1493 for(Account account : xmppConnectionService.getAccounts()) {
1494 if (account.getStatus() == Account.State.ONLINE && !xmppConnectionService.getPushManagementService().available(account)) {
1495 return true;
1496 }
1497 }
1498 return false;
1499 }
1500
1501 private void attachLocationToConversation(Conversation conversation, Uri uri) {
1502 if (conversation == null) {
1503 return;
1504 }
1505 xmppConnectionService.attachLocationToConversation(conversation,uri, new UiCallback<Message>() {
1506
1507 @Override
1508 public void success(Message message) {
1509 xmppConnectionService.sendMessage(message);
1510 }
1511
1512 @Override
1513 public void error(int errorCode, Message object) {
1514
1515 }
1516
1517 @Override
1518 public void userInputRequried(PendingIntent pi, Message object) {
1519
1520 }
1521 });
1522 }
1523
1524 private void attachFileToConversation(Conversation conversation, Uri uri) {
1525 if (conversation == null) {
1526 return;
1527 }
1528 final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG);
1529 prepareFileToast.show();
1530 delegateUriPermissionsToService(uri);
1531 xmppConnectionService.attachFileToConversation(conversation, uri, new UiInformableCallback<Message>() {
1532 @Override
1533 public void inform(final String text) {
1534 hidePrepareFileToast(prepareFileToast);
1535 runOnUiThread(new Runnable() {
1536 @Override
1537 public void run() {
1538 replaceToast(text);
1539 }
1540 });
1541 }
1542
1543 @Override
1544 public void success(Message message) {
1545 runOnUiThread(new Runnable() {
1546 @Override
1547 public void run() {
1548 hideToast();
1549 }
1550 });
1551 hidePrepareFileToast(prepareFileToast);
1552 xmppConnectionService.sendMessage(message);
1553 }
1554
1555 @Override
1556 public void error(final int errorCode, Message message) {
1557 hidePrepareFileToast(prepareFileToast);
1558 runOnUiThread(new Runnable() {
1559 @Override
1560 public void run() {
1561 replaceToast(getString(errorCode));
1562 }
1563 });
1564
1565 }
1566
1567 @Override
1568 public void userInputRequried(PendingIntent pi, Message message) {
1569 hidePrepareFileToast(prepareFileToast);
1570 }
1571 });
1572 }
1573
1574 public void attachImageToConversation(Uri uri) {
1575 this.attachImageToConversation(getSelectedConversation(), uri);
1576 }
1577
1578 private void attachImageToConversation(Conversation conversation, Uri uri) {
1579 if (conversation == null) {
1580 return;
1581 }
1582 final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG);
1583 prepareFileToast.show();
1584 delegateUriPermissionsToService(uri);
1585 xmppConnectionService.attachImageToConversation(conversation, uri,
1586 new UiCallback<Message>() {
1587
1588 @Override
1589 public void userInputRequried(PendingIntent pi, Message object) {
1590 hidePrepareFileToast(prepareFileToast);
1591 }
1592
1593 @Override
1594 public void success(Message message) {
1595 hidePrepareFileToast(prepareFileToast);
1596 xmppConnectionService.sendMessage(message);
1597 }
1598
1599 @Override
1600 public void error(final int error, Message message) {
1601 hidePrepareFileToast(prepareFileToast);
1602 runOnUiThread(new Runnable() {
1603 @Override
1604 public void run() {
1605 replaceToast(getString(error));
1606 }
1607 });
1608 }
1609 });
1610 }
1611
1612 private void hidePrepareFileToast(final Toast prepareFileToast) {
1613 if (prepareFileToast != null) {
1614 runOnUiThread(new Runnable() {
1615
1616 @Override
1617 public void run() {
1618 prepareFileToast.cancel();
1619 }
1620 });
1621 }
1622 }
1623
1624 public void updateConversationList() {
1625 xmppConnectionService.populateWithOrderedConversations(conversationList);
1626 if (!conversationList.contains(mSelectedConversation)) {
1627 mSelectedConversation = null;
1628 }
1629 if (swipedConversation != null) {
1630 if (swipedConversation.isRead()) {
1631 conversationList.remove(swipedConversation);
1632 } else {
1633 listView.discardUndo();
1634 }
1635 }
1636 listAdapter.notifyDataSetChanged();
1637 }
1638
1639 public void runIntent(PendingIntent pi, int requestCode) {
1640 try {
1641 this.startIntentSenderForResult(pi.getIntentSender(), requestCode,
1642 null, 0, 0, 0);
1643 } catch (final SendIntentException ignored) {
1644 }
1645 }
1646
1647 public void encryptTextMessage(Message message) {
1648 xmppConnectionService.getPgpEngine().encrypt(message,
1649 new UiCallback<Message>() {
1650
1651 @Override
1652 public void userInputRequried(PendingIntent pi,Message message) {
1653 ConversationActivity.this.runIntent(pi,ConversationActivity.REQUEST_SEND_MESSAGE);
1654 }
1655
1656 @Override
1657 public void success(Message message) {
1658 message.setEncryption(Message.ENCRYPTION_DECRYPTED);
1659 xmppConnectionService.sendMessage(message);
1660 runOnUiThread(new Runnable() {
1661 @Override
1662 public void run() {
1663 mConversationFragment.messageSent();
1664 }
1665 });
1666 }
1667
1668 @Override
1669 public void error(final int error, Message message) {
1670 runOnUiThread(new Runnable() {
1671 @Override
1672 public void run() {
1673 mConversationFragment.doneSendingPgpMessage();
1674 Toast.makeText(ConversationActivity.this,
1675 R.string.unable_to_connect_to_keychain,
1676 Toast.LENGTH_SHORT
1677 ).show();
1678 }
1679 });
1680
1681 }
1682 });
1683 }
1684
1685 public boolean useSendButtonToIndicateStatus() {
1686 return getPreferences().getBoolean("send_button_status", getResources().getBoolean(R.bool.send_button_status));
1687 }
1688
1689 public boolean indicateReceived() {
1690 return getPreferences().getBoolean("indicate_received", getResources().getBoolean(R.bool.indicate_received));
1691 }
1692
1693 public boolean useGreenBackground() {
1694 return getPreferences().getBoolean("use_green_background",getResources().getBoolean(R.bool.use_green_background));
1695 }
1696
1697 protected boolean trustKeysIfNeeded(int requestCode) {
1698 return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
1699 }
1700
1701 protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
1702 AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
1703 final List<Jid> targets = axolotlService.getCryptoTargets(mSelectedConversation);
1704 boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets);
1705 boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty();
1706 boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty();
1707 boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
1708 boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets);
1709 if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) {
1710 axolotlService.createSessionsIfNeeded(mSelectedConversation);
1711 Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
1712 String[] contacts = new String[targets.size()];
1713 for(int i = 0; i < contacts.length; ++i) {
1714 contacts[i] = targets.get(i).toString();
1715 }
1716 intent.putExtra("contacts", contacts);
1717 intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString());
1718 intent.putExtra("choice", attachmentChoice);
1719 intent.putExtra("conversation",mSelectedConversation.getUuid());
1720 startActivityForResult(intent, requestCode);
1721 return true;
1722 } else {
1723 return false;
1724 }
1725 }
1726
1727 @Override
1728 protected void refreshUiReal() {
1729 updateConversationList();
1730 if (conversationList.size() > 0) {
1731 if (!this.mConversationFragment.isAdded()) {
1732 Log.d(Config.LOGTAG,"fragment NOT added to activity. detached="+Boolean.toString(mConversationFragment.isDetached()));
1733 }
1734 if (getSelectedConversation() == null) {
1735 reInitLatestConversation();
1736 } else {
1737 ConversationActivity.this.mConversationFragment.updateMessages();
1738 updateActionBarTitle();
1739 invalidateOptionsMenu();
1740 }
1741 } else {
1742 if (!isStopping() && mRedirected.compareAndSet(false, true)) {
1743 redirectToStartConversationActivity(false);
1744 }
1745 Log.d(Config.LOGTAG,"not updating conversations fragment because conversations list size was 0");
1746 }
1747 }
1748
1749 @Override
1750 public void onAccountUpdate() {
1751 this.refreshUi();
1752 }
1753
1754 @Override
1755 public void onConversationUpdate() {
1756 this.refreshUi();
1757 }
1758
1759 @Override
1760 public void onRosterUpdate() {
1761 this.refreshUi();
1762 }
1763
1764 @Override
1765 public void OnUpdateBlocklist(Status status) {
1766 this.refreshUi();
1767 }
1768
1769 public void unblockConversation(final Blockable conversation) {
1770 xmppConnectionService.sendUnblockRequest(conversation);
1771 }
1772
1773 public boolean enterIsSend() {
1774 return getPreferences().getBoolean("enter_is_send",getResources().getBoolean(R.bool.enter_is_send));
1775 }
1776
1777 @Override
1778 public void onShowErrorToast(final int resId) {
1779 runOnUiThread(new Runnable() {
1780 @Override
1781 public void run() {
1782 Toast.makeText(ConversationActivity.this,resId,Toast.LENGTH_SHORT).show();
1783 }
1784 });
1785 }
1786
1787 public boolean highlightSelectedConversations() {
1788 return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard;
1789 }
1790}