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