ConversationsActivity.java

  1/*
  2 * Copyright (c) 2018, Daniel Gultsch All rights reserved.
  3 *
  4 * Redistribution and use in source and binary forms, with or without modification,
  5 * are permitted provided that the following conditions are met:
  6 *
  7 * 1. Redistributions of source code must retain the above copyright notice, this
  8 * list of conditions and the following disclaimer.
  9 *
 10 * 2. Redistributions in binary form must reproduce the above copyright notice,
 11 * this list of conditions and the following disclaimer in the documentation and/or
 12 * other materials provided with the distribution.
 13 *
 14 * 3. Neither the name of the copyright holder nor the names of its contributors
 15 * may be used to endorse or promote products derived from this software without
 16 * specific prior written permission.
 17 *
 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 21 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
 22 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 25 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28 */
 29
 30package eu.siacs.conversations.ui;
 31
 32
 33import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
 34
 35import android.annotation.SuppressLint;
 36import android.app.Activity;
 37import android.app.Fragment;
 38import android.app.FragmentManager;
 39import android.app.FragmentTransaction;
 40import android.content.ActivityNotFoundException;
 41import android.content.Context;
 42import android.content.Intent;
 43import android.content.pm.PackageManager;
 44import android.net.Uri;
 45import android.os.Bundle;
 46import android.provider.Settings;
 47import android.util.Log;
 48import android.view.KeyEvent;
 49import android.view.Menu;
 50import android.view.MenuItem;
 51import android.widget.Toast;
 52
 53import androidx.annotation.IdRes;
 54import androidx.annotation.NonNull;
 55import androidx.appcompat.app.ActionBar;
 56import androidx.appcompat.app.AlertDialog;
 57import androidx.databinding.DataBindingUtil;
 58
 59import org.openintents.openpgp.util.OpenPgpApi;
 60
 61import java.util.Arrays;
 62import java.util.List;
 63import java.util.concurrent.atomic.AtomicBoolean;
 64
 65import eu.siacs.conversations.Config;
 66import eu.siacs.conversations.R;
 67import eu.siacs.conversations.crypto.OmemoSetting;
 68import eu.siacs.conversations.databinding.ActivityConversationsBinding;
 69import eu.siacs.conversations.entities.Account;
 70import eu.siacs.conversations.entities.Contact;
 71import eu.siacs.conversations.entities.Conversation;
 72import eu.siacs.conversations.entities.Conversational;
 73import eu.siacs.conversations.services.XmppConnectionService;
 74import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
 75import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
 76import eu.siacs.conversations.ui.interfaces.OnConversationRead;
 77import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
 78import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
 79import eu.siacs.conversations.ui.util.ActionBarUtil;
 80import eu.siacs.conversations.ui.util.ActivityResult;
 81import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
 82import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 83import eu.siacs.conversations.ui.util.PendingItem;
 84import eu.siacs.conversations.utils.ExceptionHelper;
 85import eu.siacs.conversations.utils.SignupUtils;
 86import eu.siacs.conversations.utils.XmppUri;
 87import eu.siacs.conversations.xmpp.Jid;
 88import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 89
 90public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged {
 91
 92    public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
 93    public static final String EXTRA_CONVERSATION = "conversationUuid";
 94    public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
 95    public static final String EXTRA_AS_QUOTE = "eu.siacs.conversations.as_quote";
 96    public static final String EXTRA_NICK = "nick";
 97    public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm";
 98    public static final String EXTRA_DO_NOT_APPEND = "do_not_append";
 99    public static final String EXTRA_POST_INIT_ACTION = "post_init_action";
100    public static final String POST_ACTION_RECORD_VOICE = "record_voice";
101    public static final String EXTRA_TYPE = "type";
102    public static final String EXTRA_NODE = "node";
103    public static final String EXTRA_JID = "jid";
104
105    private static final List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
106            ACTION_VIEW_CONVERSATION,
107            Intent.ACTION_SEND,
108            Intent.ACTION_SEND_MULTIPLE
109    );
110
111    public static final int REQUEST_OPEN_MESSAGE = 0x9876;
112    public static final int REQUEST_PLAY_PAUSE = 0x5432;
113
114
115    //secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
116    private static final @IdRes
117    int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
118    private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
119    private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
120    private ActivityConversationsBinding binding;
121    private boolean mActivityPaused = true;
122    private final AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
123    private boolean refreshForNewCaps = false;
124
125    private static boolean isViewOrShareIntent(Intent i) {
126        Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction()));
127        return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
128    }
129
130    private static Intent createLauncherIntent(Context context) {
131        final Intent intent = new Intent(context, ConversationsActivity.class);
132        intent.setAction(Intent.ACTION_MAIN);
133        intent.addCategory(Intent.CATEGORY_LAUNCHER);
134        return intent;
135    }
136
137    @Override
138    protected void refreshUiReal() {
139        invalidateOptionsMenu();
140        for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
141            refreshFragment(id);
142        }
143        refreshForNewCaps = false;
144    }
145
146    @Override
147    void onBackendConnected() {
148        if (performRedirectIfNecessary(true)) {
149            return;
150        }
151        xmppConnectionService.getNotificationService().setIsInForeground(true);
152        final Intent intent = pendingViewIntent.pop();
153        if (intent != null) {
154            if (processViewIntent(intent)) {
155                if (binding.secondaryFragment != null) {
156                    notifyFragmentOfBackendConnected(R.id.main_fragment);
157                }
158                invalidateActionBarTitle();
159                return;
160            }
161        }
162        for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
163            notifyFragmentOfBackendConnected(id);
164        }
165
166        final ActivityResult activityResult = postponedActivityResult.pop();
167        if (activityResult != null) {
168            handleActivityResult(activityResult);
169        }
170
171        invalidateActionBarTitle();
172        if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
173            Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
174            if (conversation != null) {
175                openConversation(conversation, null);
176            }
177        }
178        showDialogsIfMainIsOverview();
179    }
180
181    private boolean performRedirectIfNecessary(boolean noAnimation) {
182        return performRedirectIfNecessary(null, noAnimation);
183    }
184
185    private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) {
186        if (xmppConnectionService == null) {
187            return false;
188        }
189        boolean isConversationsListEmpty = xmppConnectionService.isConversationsListEmpty(ignore);
190        if (isConversationsListEmpty && mRedirectInProcess.compareAndSet(false, true)) {
191            final Intent intent = SignupUtils.getRedirectionIntent(this);
192            if (noAnimation) {
193                intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
194            }
195            runOnUiThread(() -> {
196                startActivity(intent);
197                if (noAnimation) {
198                    overridePendingTransition(0, 0);
199                }
200            });
201        }
202        return mRedirectInProcess.get();
203    }
204
205    private void showDialogsIfMainIsOverview() {
206        if (xmppConnectionService == null) {
207            return;
208        }
209        final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
210        if (fragment instanceof ConversationsOverviewFragment) {
211            if (ExceptionHelper.checkForCrash(this)) {
212                return;
213            }
214            openBatteryOptimizationDialogIfNeeded();
215        }
216    }
217
218    private String getBatteryOptimizationPreferenceKey() {
219        @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
220        return "show_battery_optimization" + (device == null ? "" : device);
221    }
222
223    private void setNeverAskForBatteryOptimizationsAgain() {
224        getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
225    }
226
227    private void openBatteryOptimizationDialogIfNeeded() {
228        if (isOptimizingBattery()
229                && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M
230                && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
231            AlertDialog.Builder builder = new AlertDialog.Builder(this);
232            builder.setTitle(R.string.battery_optimizations_enabled);
233            builder.setMessage(getString(R.string.battery_optimizations_enabled_dialog, getString(R.string.app_name)));
234            builder.setPositiveButton(R.string.next, (dialog, which) -> {
235                Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
236                Uri uri = Uri.parse("package:" + getPackageName());
237                intent.setData(uri);
238                try {
239                    startActivityForResult(intent, REQUEST_BATTERY_OP);
240                } catch (ActivityNotFoundException e) {
241                    Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
242                }
243            });
244            builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
245            final AlertDialog dialog = builder.create();
246            dialog.setCanceledOnTouchOutside(false);
247            dialog.show();
248        }
249    }
250
251    private void notifyFragmentOfBackendConnected(@IdRes int id) {
252        final Fragment fragment = getFragmentManager().findFragmentById(id);
253        if (fragment instanceof OnBackendConnected) {
254            ((OnBackendConnected) fragment).onBackendConnected();
255        }
256    }
257
258    private void refreshFragment(@IdRes int id) {
259        final Fragment fragment = getFragmentManager().findFragmentById(id);
260        if (fragment instanceof XmppFragment) {
261            ((XmppFragment) fragment).refresh();
262            if (refreshForNewCaps) ((XmppFragment) fragment).refreshForNewCaps();
263        }
264    }
265
266    private boolean processViewIntent(Intent intent) {
267        final String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
268        final Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
269        if (conversation == null) {
270            Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
271            return false;
272        }
273        openConversation(conversation, intent.getExtras());
274        return true;
275    }
276
277    @Override
278    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
279        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
280        UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
281        if (grantResults.length > 0) {
282            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
283                switch (requestCode) {
284                    case REQUEST_OPEN_MESSAGE:
285                        refreshUiReal();
286                        ConversationFragment.openPendingMessage(this);
287                        break;
288                    case REQUEST_PLAY_PAUSE:
289                        ConversationFragment.startStopPending(this);
290                        break;
291                }
292            }
293        }
294    }
295
296    @Override
297    public void onActivityResult(int requestCode, int resultCode, final Intent data) {
298        super.onActivityResult(requestCode, resultCode, data);
299        ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data);
300        if (xmppConnectionService != null) {
301            handleActivityResult(activityResult);
302        } else {
303            this.postponedActivityResult.push(activityResult);
304        }
305    }
306
307    private void handleActivityResult(ActivityResult activityResult) {
308        if (activityResult.resultCode == Activity.RESULT_OK) {
309            handlePositiveActivityResult(activityResult.requestCode, activityResult.data);
310        } else {
311            handleNegativeActivityResult(activityResult.requestCode);
312        }
313    }
314
315    private void handleNegativeActivityResult(int requestCode) {
316        Conversation conversation = ConversationFragment.getConversationReliable(this);
317        switch (requestCode) {
318            case REQUEST_DECRYPT_PGP:
319                if (conversation == null) {
320                    break;
321                }
322                conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption();
323                break;
324            case REQUEST_BATTERY_OP:
325                setNeverAskForBatteryOptimizationsAgain();
326                break;
327        }
328    }
329
330    private void handlePositiveActivityResult(int requestCode, final Intent data) {
331        Conversation conversation = ConversationFragment.getConversationReliable(this);
332        if (conversation == null) {
333            Log.d(Config.LOGTAG, "conversation not found");
334            return;
335        }
336        switch (requestCode) {
337            case REQUEST_DECRYPT_PGP:
338                conversation.getAccount().getPgpDecryptionService().continueDecryption(data);
339                break;
340            case REQUEST_CHOOSE_PGP_ID:
341                long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0);
342                if (id != 0) {
343                    conversation.getAccount().setPgpSignId(id);
344                    announcePgp(conversation.getAccount(), null, null, onOpenPGPKeyPublished);
345                } else {
346                    choosePgpSignId(conversation.getAccount());
347                }
348                break;
349            case REQUEST_ANNOUNCE_PGP:
350                announcePgp(conversation.getAccount(), conversation, data, onOpenPGPKeyPublished);
351                break;
352        }
353    }
354
355    @Override
356    protected void onCreate(final Bundle savedInstanceState) {
357        super.onCreate(savedInstanceState);
358        ConversationMenuConfigurator.reloadFeatures(this);
359        OmemoSetting.load(this);
360        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
361        setSupportActionBar(binding.toolbar);
362        configureActionBar(getSupportActionBar());
363        this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
364        this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
365        this.initializeFragments();
366        this.invalidateActionBarTitle();
367        final Intent intent;
368        if (savedInstanceState == null) {
369            intent = getIntent();
370        } else {
371            intent = savedInstanceState.getParcelable("intent");
372        }
373        if (isViewOrShareIntent(intent)) {
374            pendingViewIntent.push(intent);
375            setIntent(createLauncherIntent(this));
376        }
377    }
378
379    @Override
380    public boolean onCreateOptionsMenu(Menu menu) {
381        getMenuInflater().inflate(R.menu.activity_conversations, menu);
382        final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
383        if (qrCodeScanMenuItem != null) {
384            if (isCameraFeatureAvailable()) {
385                Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
386                boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan)
387                        && fragment instanceof ConversationsOverviewFragment;
388                qrCodeScanMenuItem.setVisible(visible);
389            } else {
390                qrCodeScanMenuItem.setVisible(false);
391            }
392        }
393        return super.onCreateOptionsMenu(menu);
394    }
395
396    @Override
397    public void onConversationSelected(Conversation conversation) {
398        clearPendingViewIntent();
399        if (ConversationFragment.getConversation(this) == conversation) {
400            Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
401            return;
402        }
403        openConversation(conversation, null);
404    }
405
406    public void clearPendingViewIntent() {
407        if (pendingViewIntent.clear()) {
408            Log.e(Config.LOGTAG, "cleared pending view intent");
409        }
410    }
411
412    private void displayToast(final String msg) {
413        runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
414    }
415
416    @Override
417    public void onAffiliationChangedSuccessful(Jid jid) {
418
419    }
420
421    @Override
422    public void onAffiliationChangeFailed(Jid jid, int resId) {
423        displayToast(getString(resId, jid.asBareJid().toString()));
424    }
425
426    private void openConversation(Conversation conversation, Bundle extras) {
427        final FragmentManager fragmentManager = getFragmentManager();
428        executePendingTransactions(fragmentManager);
429        ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment);
430        final boolean mainNeedsRefresh;
431        if (conversationFragment == null) {
432            mainNeedsRefresh = false;
433            final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
434            if (mainFragment instanceof ConversationFragment) {
435                conversationFragment = (ConversationFragment) mainFragment;
436            } else {
437                conversationFragment = new ConversationFragment();
438                FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
439                fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
440                fragmentTransaction.addToBackStack(null);
441                try {
442                    fragmentTransaction.commit();
443                } catch (IllegalStateException e) {
444                    Log.w(Config.LOGTAG, "sate loss while opening conversation", e);
445                    //allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored'
446                    return;
447                }
448            }
449        } else {
450            mainNeedsRefresh = true;
451        }
452        conversationFragment.reInit(conversation, extras == null ? new Bundle() : extras);
453        if (mainNeedsRefresh) {
454            refreshFragment(R.id.main_fragment);
455        } else {
456            invalidateActionBarTitle();
457        }
458    }
459
460    private static void executePendingTransactions(final FragmentManager fragmentManager) {
461        try {
462            fragmentManager.executePendingTransactions();
463        } catch (final Exception e) {
464            Log.e(Config.LOGTAG,"unable to execute pending fragment transactions");
465        }
466    }
467
468    public boolean onXmppUriClicked(Uri uri) {
469        XmppUri xmppUri = new XmppUri(uri);
470        if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) {
471            final Conversation conversation = xmppConnectionService.findUniqueConversationByJid(xmppUri);
472            if (conversation != null) {
473                if (xmppUri.isAction("command")) {
474                    startCommand(conversation.getAccount(), conversation.getJid(), xmppUri.getParameter("node"));
475                } else {
476                    Bundle extras = new Bundle();
477                    extras.putString(Intent.EXTRA_TEXT, xmppUri.getBody());
478                    if (xmppUri.isAction("message")) extras.putString(EXTRA_POST_INIT_ACTION, "message");
479                    openConversation(conversation, extras);
480                }
481                return true;
482            }
483        }
484        return false;
485    }
486
487    @Override
488    public boolean onOptionsItemSelected(MenuItem item) {
489        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
490            return false;
491        }
492        switch (item.getItemId()) {
493            case android.R.id.home:
494                FragmentManager fm = getFragmentManager();
495                if (fm.getBackStackEntryCount() > 0) {
496                    try {
497                        fm.popBackStack();
498                    } catch (IllegalStateException e) {
499                        Log.w(Config.LOGTAG, "Unable to pop back stack after pressing home button");
500                    }
501                    return true;
502                }
503                break;
504            case R.id.action_scan_qr_code:
505                UriHandlerActivity.scan(this);
506                return true;
507            case R.id.action_search_all_conversations:
508                startActivity(new Intent(this, SearchActivity.class));
509                return true;
510            case R.id.action_search_this_conversation:
511                final Conversation conversation = ConversationFragment.getConversation(this);
512                if (conversation == null) {
513                    return true;
514                }
515                final Intent intent = new Intent(this, SearchActivity.class);
516                intent.putExtra(SearchActivity.EXTRA_CONVERSATION_UUID, conversation.getUuid());
517                startActivity(intent);
518                return true;
519        }
520        return super.onOptionsItemSelected(item);
521    }
522
523    @Override
524    public boolean onKeyDown(final int keyCode, final KeyEvent keyEvent) {
525        if (keyCode == KeyEvent.KEYCODE_DPAD_UP && keyEvent.isCtrlPressed()) {
526            final ConversationFragment conversationFragment = ConversationFragment.get(this);
527            if (conversationFragment != null && conversationFragment.onArrowUpCtrlPressed()) {
528                return true;
529            }
530        }
531        return super.onKeyDown(keyCode, keyEvent);
532    }
533
534    @Override
535    public void onSaveInstanceState(final Bundle savedInstanceState) {
536        final Intent pendingIntent = pendingViewIntent.peek();
537        savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
538        super.onSaveInstanceState(savedInstanceState);
539    }
540
541    @Override
542    protected void onStart() {
543        super.onStart();
544        final int theme = findTheme();
545        if (this.mTheme != theme) {
546            this.mSkipBackgroundBinding = true;
547            recreate();
548        } else {
549            this.mSkipBackgroundBinding = false;
550        }
551        mRedirectInProcess.set(false);
552    }
553
554    @Override
555    protected void onNewIntent(final Intent intent) {
556        super.onNewIntent(intent);
557        if (isViewOrShareIntent(intent)) {
558            if (xmppConnectionService != null) {
559                clearPendingViewIntent();
560                processViewIntent(intent);
561            } else {
562                pendingViewIntent.push(intent);
563            }
564        }
565        setIntent(createLauncherIntent(this));
566    }
567
568    @Override
569    public void onPause() {
570        this.mActivityPaused = true;
571        super.onPause();
572    }
573
574    @Override
575    public void onResume() {
576        super.onResume();
577        this.mActivityPaused = false;
578    }
579
580    private void initializeFragments() {
581        final FragmentManager fragmentManager = getFragmentManager();
582        FragmentTransaction transaction = fragmentManager.beginTransaction();
583        final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
584        final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
585        if (mainFragment != null) {
586            if (binding.secondaryFragment != null) {
587                if (mainFragment instanceof ConversationFragment) {
588                    getFragmentManager().popBackStack();
589                    transaction.remove(mainFragment);
590                    transaction.commit();
591                    fragmentManager.executePendingTransactions();
592                    transaction = fragmentManager.beginTransaction();
593                    transaction.replace(R.id.secondary_fragment, mainFragment);
594                    transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
595                    transaction.commit();
596                    return;
597                }
598            } else {
599                if (secondaryFragment instanceof ConversationFragment) {
600                    transaction.remove(secondaryFragment);
601                    transaction.commit();
602                    getFragmentManager().executePendingTransactions();
603                    transaction = fragmentManager.beginTransaction();
604                    transaction.replace(R.id.main_fragment, secondaryFragment);
605                    transaction.addToBackStack(null);
606                    transaction.commit();
607                    return;
608                }
609            }
610        } else {
611            transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
612        }
613        if (binding.secondaryFragment != null && secondaryFragment == null) {
614            transaction.replace(R.id.secondary_fragment, new ConversationFragment());
615        }
616        transaction.commit();
617    }
618
619    private void invalidateActionBarTitle() {
620        final ActionBar actionBar = getSupportActionBar();
621        if (actionBar == null) {
622            return;
623        }
624        final FragmentManager fragmentManager = getFragmentManager();
625        final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
626        if (mainFragment instanceof ConversationFragment) {
627            final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
628            if (conversation != null) {
629                actionBar.setTitle(conversation.getName());
630                actionBar.setDisplayHomeAsUpEnabled(true);
631                ActionBarUtil.setActionBarOnClickListener(
632                        binding.toolbar,
633                        (v) -> openConversationDetails(conversation)
634                );
635                return;
636            }
637        }
638        actionBar.setTitle(R.string.app_name);
639        actionBar.setDisplayHomeAsUpEnabled(false);
640        ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar);
641    }
642
643    private void openConversationDetails(final Conversation conversation) {
644        if (conversation.getMode() == Conversational.MODE_MULTI) {
645            ConferenceDetailsActivity.open(this, conversation);
646        } else {
647            final Contact contact = conversation.getContact();
648            if (contact.isSelf()) {
649                switchToAccount(conversation.getAccount());
650            } else {
651                switchToContactDetails(contact);
652            }
653        }
654    }
655
656    @Override
657    public void onConversationArchived(Conversation conversation) {
658        if (performRedirectIfNecessary(conversation, false)) {
659            return;
660        }
661        final FragmentManager fragmentManager = getFragmentManager();
662        final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
663        if (mainFragment instanceof ConversationFragment) {
664            try {
665                fragmentManager.popBackStack();
666            } catch (final IllegalStateException e) {
667                Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e);
668                //this usually means activity is no longer active; meaning on the next open we will run through this again
669            }
670            return;
671        }
672        final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
673        if (secondaryFragment instanceof ConversationFragment) {
674            if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
675                Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
676                if (suggestion != null) {
677                    openConversation(suggestion, null);
678                }
679            }
680        }
681    }
682
683    @Override
684    public void onConversationsListItemUpdated() {
685        Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
686        if (fragment instanceof ConversationsOverviewFragment) {
687            ((ConversationsOverviewFragment) fragment).refresh();
688        }
689    }
690
691    @Override
692    public void switchToConversation(Conversation conversation) {
693        Log.d(Config.LOGTAG, "override");
694        openConversation(conversation, null);
695    }
696
697    @Override
698    public void onConversationRead(Conversation conversation, String upToUuid) {
699        if (!mActivityPaused && pendingViewIntent.peek() == null) {
700            xmppConnectionService.sendReadMarker(conversation, upToUuid);
701        } else {
702            Log.d(Config.LOGTAG, "ignoring read callback. mActivityPaused=" + mActivityPaused);
703        }
704    }
705
706    @Override
707    public void onAccountUpdate() {
708        this.refreshUi();
709    }
710
711    @Override
712    public void onConversationUpdate(boolean newCaps) {
713        if (performRedirectIfNecessary(false)) {
714            return;
715        }
716        refreshForNewCaps = newCaps;
717        this.refreshUi();
718    }
719
720    @Override
721    public void onRosterUpdate() {
722        refreshForNewCaps = true;
723        this.refreshUi();
724    }
725
726    @Override
727    public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
728        this.refreshUi();
729    }
730
731    @Override
732    public void onShowErrorToast(int resId) {
733        runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
734    }
735}