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