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