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