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