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 android.annotation.SuppressLint;
 34import android.app.Activity;
 35import android.app.Fragment;
 36import android.app.FragmentManager;
 37import android.app.FragmentTransaction;
 38import android.content.ActivityNotFoundException;
 39import android.content.Context;
 40import android.content.Intent;
 41import android.content.pm.PackageManager;
 42import android.databinding.DataBindingUtil;
 43import android.net.Uri;
 44import android.os.Bundle;
 45import android.provider.Settings;
 46import android.support.annotation.IdRes;
 47import android.support.annotation.NonNull;
 48import android.support.v7.app.ActionBar;
 49import android.support.v7.app.AlertDialog;
 50import android.support.v7.widget.Toolbar;
 51import android.util.Log;
 52import android.view.Menu;
 53import android.view.MenuItem;
 54import android.widget.Toast;
 55
 56import org.openintents.openpgp.util.OpenPgpApi;
 57
 58import java.util.Arrays;
 59import java.util.List;
 60import java.util.concurrent.atomic.AtomicBoolean;
 61
 62import eu.siacs.conversations.Config;
 63import eu.siacs.conversations.R;
 64import eu.siacs.conversations.crypto.OmemoSetting;
 65import eu.siacs.conversations.databinding.ActivityConversationsBinding;
 66import eu.siacs.conversations.entities.Account;
 67import eu.siacs.conversations.entities.Conversation;
 68import eu.siacs.conversations.services.XmppConnectionService;
 69import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
 70import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
 71import eu.siacs.conversations.ui.interfaces.OnConversationRead;
 72import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
 73import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
 74import eu.siacs.conversations.ui.service.EmojiService;
 75import eu.siacs.conversations.ui.util.ActivityResult;
 76import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
 77import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
 78import eu.siacs.conversations.ui.util.PendingItem;
 79import eu.siacs.conversations.utils.EmojiWrapper;
 80import eu.siacs.conversations.utils.ExceptionHelper;
 81import eu.siacs.conversations.utils.XmppUri;
 82import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 83import rocks.xmpp.addr.Jid;
 84
 85import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
 86
 87public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged {
 88
 89    public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
 90    public static final String EXTRA_CONVERSATION = "conversationUuid";
 91    public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
 92    public static final String EXTRA_AS_QUOTE = "as_quote";
 93    public static final String EXTRA_NICK = "nick";
 94    public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm";
 95
 96    private static List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
 97            ACTION_VIEW_CONVERSATION,
 98            Intent.ACTION_SEND,
 99            Intent.ACTION_SEND_MULTIPLE
100    );
101
102    public static final int REQUEST_OPEN_MESSAGE = 0x9876;
103    public static final int REQUEST_PLAY_PAUSE = 0x5432;
104
105
106    //secondary fragment (when holding the conversation, must be initialized before refreshing the overview fragment
107    private static final @IdRes
108    int[] FRAGMENT_ID_NOTIFICATION_ORDER = {R.id.secondary_fragment, R.id.main_fragment};
109    private final PendingItem<Intent> pendingViewIntent = new PendingItem<>();
110    private final PendingItem<ActivityResult> postponedActivityResult = new PendingItem<>();
111    private ActivityConversationsBinding binding;
112    private boolean mActivityPaused = true;
113    private AtomicBoolean mRedirectInProcess = new AtomicBoolean(false);
114
115    private static boolean isViewOrShareIntent(Intent i) {
116        Log.d(Config.LOGTAG, "action: " + (i == null ? null : i.getAction()));
117        return i != null && VIEW_AND_SHARE_ACTIONS.contains(i.getAction()) && i.hasExtra(EXTRA_CONVERSATION);
118    }
119
120    private static Intent createLauncherIntent(Context context) {
121        final Intent intent = new Intent(context, ConversationsActivity.class);
122        intent.setAction(Intent.ACTION_MAIN);
123        intent.addCategory(Intent.CATEGORY_LAUNCHER);
124        return intent;
125    }
126
127    @Override
128    protected void refreshUiReal() {
129        for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
130            refreshFragment(id);
131        }
132    }
133
134    @Override
135    void onBackendConnected() {
136        if (performRedirectIfNecessary(true)) {
137            return;
138        }
139        xmppConnectionService.getNotificationService().setIsInForeground(true);
140        Intent intent = pendingViewIntent.pop();
141        if (intent != null) {
142            if (processViewIntent(intent)) {
143                if (binding.secondaryFragment != null) {
144                    notifyFragmentOfBackendConnected(R.id.main_fragment);
145                }
146                invalidateActionBarTitle();
147                return;
148            }
149        }
150        for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
151            notifyFragmentOfBackendConnected(id);
152        }
153
154        ActivityResult activityResult = postponedActivityResult.pop();
155        if (activityResult != null) {
156            handleActivityResult(activityResult);
157        }
158
159        invalidateActionBarTitle();
160        if (binding.secondaryFragment != null && ConversationFragment.getConversation(this) == null) {
161            Conversation conversation = ConversationsOverviewFragment.getSuggestion(this);
162            if (conversation != null) {
163                openConversation(conversation, null);
164            }
165        }
166        showDialogsIfMainIsOverview();
167    }
168
169    private boolean performRedirectIfNecessary(boolean noAnimation) {
170        return performRedirectIfNecessary(null, noAnimation);
171    }
172
173    private boolean performRedirectIfNecessary(final Conversation ignore, final boolean noAnimation) {
174        if (xmppConnectionService == null) {
175            return false;
176        }
177        boolean isConversationsListEmpty = xmppConnectionService.isConversationsListEmpty(ignore);
178        if (isConversationsListEmpty && mRedirectInProcess.compareAndSet(false, true)) {
179            final Intent intent = getRedirectionIntent(noAnimation);
180            runOnUiThread(() -> {
181                startActivity(intent);
182                if (noAnimation) {
183                    overridePendingTransition(0, 0);
184                }
185            });
186        }
187        return mRedirectInProcess.get();
188    }
189
190    private Intent getRedirectionIntent(boolean noAnimation) {
191        Account pendingAccount = xmppConnectionService.getPendingAccount();
192        Intent intent;
193        if (pendingAccount != null) {
194            intent = new Intent(this, EditAccountActivity.class);
195            intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString());
196        } else {
197            if (xmppConnectionService.getAccounts().size() == 0) {
198                if (Config.X509_VERIFICATION) {
199                    intent = new Intent(this, ManageAccountActivity.class);
200                } else if (Config.MAGIC_CREATE_DOMAIN != null) {
201                    intent = new Intent(this, WelcomeActivity.class);
202                    WelcomeActivity.addInviteUri(intent, getIntent());
203                } else {
204                    intent = new Intent(this, EditAccountActivity.class);
205                }
206            } else {
207                intent = new Intent(this, StartConversationActivity.class);
208            }
209        }
210        intent.putExtra("init", true);
211        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
212        if (noAnimation) {
213            intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
214        }
215        return intent;
216    }
217
218    private void showDialogsIfMainIsOverview() {
219        if (xmppConnectionService == null) {
220            return;
221        }
222        final Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
223        if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
224            if (ExceptionHelper.checkForCrash(this)) {
225                return;
226            }
227            openBatteryOptimizationDialogIfNeeded();
228        }
229    }
230
231    private String getBatteryOptimizationPreferenceKey() {
232        @SuppressLint("HardwareIds") String device = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
233        return "show_battery_optimization" + (device == null ? "" : device);
234    }
235
236    private void setNeverAskForBatteryOptimizationsAgain() {
237        getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
238    }
239
240    private void openBatteryOptimizationDialogIfNeeded() {
241        if (hasAccountWithoutPush()
242                && isOptimizingBattery()
243                && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
244            AlertDialog.Builder builder = new AlertDialog.Builder(this);
245            builder.setTitle(R.string.battery_optimizations_enabled);
246            builder.setMessage(R.string.battery_optimizations_enabled_dialog);
247            builder.setPositiveButton(R.string.next, (dialog, which) -> {
248                Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
249                Uri uri = Uri.parse("package:" + getPackageName());
250                intent.setData(uri);
251                try {
252                    startActivityForResult(intent, REQUEST_BATTERY_OP);
253                } catch (ActivityNotFoundException e) {
254                    Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
255                }
256            });
257            builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
258            final AlertDialog dialog = builder.create();
259            dialog.setCanceledOnTouchOutside(false);
260            dialog.show();
261        }
262    }
263
264    private boolean hasAccountWithoutPush() {
265        for (Account account : xmppConnectionService.getAccounts()) {
266            if (account.getStatus() == Account.State.ONLINE && !xmppConnectionService.getPushManagementService().available(account)) {
267                return true;
268            }
269        }
270        return false;
271    }
272
273    private void notifyFragmentOfBackendConnected(@IdRes int id) {
274        final Fragment fragment = getFragmentManager().findFragmentById(id);
275        if (fragment != null && fragment instanceof OnBackendConnected) {
276            ((OnBackendConnected) fragment).onBackendConnected();
277        }
278    }
279
280    private void refreshFragment(@IdRes int id) {
281        final Fragment fragment = getFragmentManager().findFragmentById(id);
282        if (fragment != null && fragment instanceof XmppFragment) {
283            ((XmppFragment) fragment).refresh();
284        }
285    }
286
287    private boolean processViewIntent(Intent intent) {
288        String uuid = intent.getStringExtra(EXTRA_CONVERSATION);
289        Conversation conversation = uuid != null ? xmppConnectionService.findConversationByUuid(uuid) : null;
290        if (conversation == null) {
291            Log.d(Config.LOGTAG, "unable to view conversation with uuid:" + uuid);
292            return false;
293        }
294        openConversation(conversation, intent.getExtras());
295        return true;
296    }
297
298    @Override
299    public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
300        UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
301        if (grantResults.length > 0) {
302            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
303                switch (requestCode) {
304                    case REQUEST_OPEN_MESSAGE:
305                        refreshUiReal();
306                        ConversationFragment.openPendingMessage(this);
307                        break;
308                    case REQUEST_PLAY_PAUSE:
309                        ConversationFragment.startStopPending(this);
310                        break;
311                }
312            }
313        }
314    }
315
316    @Override
317    public void onActivityResult(int requestCode, int resultCode, final Intent data) {
318        super.onActivityResult(requestCode, resultCode, data);
319        ActivityResult activityResult = ActivityResult.of(requestCode, resultCode, data);
320        if (xmppConnectionService != null) {
321            handleActivityResult(activityResult);
322        } else {
323            this.postponedActivityResult.push(activityResult);
324        }
325    }
326
327    private void handleActivityResult(ActivityResult activityResult) {
328        if (activityResult.resultCode == Activity.RESULT_OK) {
329            handlePositiveActivityResult(activityResult.requestCode, activityResult.data);
330        } else {
331            handleNegativeActivityResult(activityResult.requestCode);
332        }
333    }
334
335    private void handleNegativeActivityResult(int requestCode) {
336        Conversation conversation = ConversationFragment.getConversationReliable(this);
337        switch (requestCode) {
338            case REQUEST_DECRYPT_PGP:
339                if (conversation == null) {
340                    break;
341                }
342                conversation.getAccount().getPgpDecryptionService().giveUpCurrentDecryption();
343                break;
344            case REQUEST_BATTERY_OP:
345                setNeverAskForBatteryOptimizationsAgain();
346                break;
347        }
348    }
349
350    private void handlePositiveActivityResult(int requestCode, final Intent data) {
351        Conversation conversation = ConversationFragment.getConversationReliable(this);
352        if (conversation == null) {
353            Log.d(Config.LOGTAG, "conversation not found");
354            return;
355        }
356        switch (requestCode) {
357            case REQUEST_DECRYPT_PGP:
358                conversation.getAccount().getPgpDecryptionService().continueDecryption(data);
359                break;
360            case REQUEST_CHOOSE_PGP_ID:
361                long id = data.getLongExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, 0);
362                if (id != 0) {
363                    conversation.getAccount().setPgpSignId(id);
364                    announcePgp(conversation.getAccount(), null, null, onOpenPGPKeyPublished);
365                } else {
366                    choosePgpSignId(conversation.getAccount());
367                }
368                break;
369            case REQUEST_ANNOUNCE_PGP:
370                announcePgp(conversation.getAccount(), conversation, data, onOpenPGPKeyPublished);
371                break;
372        }
373    }
374
375    @Override
376    protected void onCreate(final Bundle savedInstanceState) {
377        super.onCreate(savedInstanceState);
378        ConversationMenuConfigurator.reloadFeatures(this);
379        OmemoSetting.load(this);
380        new EmojiService(this).init();
381        this.binding = DataBindingUtil.setContentView(this, R.layout.activity_conversations);
382        setSupportActionBar((Toolbar) binding.toolbar);
383        configureActionBar(getSupportActionBar());
384        this.getFragmentManager().addOnBackStackChangedListener(this::invalidateActionBarTitle);
385        this.getFragmentManager().addOnBackStackChangedListener(this::showDialogsIfMainIsOverview);
386        this.initializeFragments();
387        this.invalidateActionBarTitle();
388        final Intent intent;
389        if (savedInstanceState == null) {
390            intent = getIntent();
391        } else {
392            intent = savedInstanceState.getParcelable("intent");
393        }
394        if (isViewOrShareIntent(intent)) {
395            pendingViewIntent.push(intent);
396            setIntent(createLauncherIntent(this));
397        }
398    }
399
400    @Override
401    public boolean onCreateOptionsMenu(Menu menu) {
402        getMenuInflater().inflate(R.menu.activity_conversations, menu);
403        MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
404        if (qrCodeScanMenuItem != null) {
405            if (isCameraFeatureAvailable()) {
406                Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
407                boolean visible = getResources().getBoolean(R.bool.show_qr_code_scan)
408                        && fragment != null
409                        && fragment instanceof ConversationsOverviewFragment;
410                qrCodeScanMenuItem.setVisible(visible);
411            } else {
412                qrCodeScanMenuItem.setVisible(false);
413            }
414        }
415        return super.onCreateOptionsMenu(menu);
416    }
417
418    @Override
419    public void onConversationSelected(Conversation conversation) {
420        clearPendingViewIntent();
421        if (ConversationFragment.getConversation(this) == conversation) {
422            Log.d(Config.LOGTAG, "ignore onConversationSelected() because conversation is already open");
423            return;
424        }
425        openConversation(conversation, null);
426    }
427
428    public void clearPendingViewIntent() {
429        if (pendingViewIntent.clear()) {
430            Log.e(Config.LOGTAG, "cleared pending view intent");
431        }
432    }
433
434    private void displayToast(final String msg) {
435        runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
436    }
437
438    @Override
439    public void onAffiliationChangedSuccessful(Jid jid) {
440
441    }
442
443    @Override
444    public void onAffiliationChangeFailed(Jid jid, int resId) {
445        displayToast(getString(resId, jid.asBareJid().toString()));
446    }
447
448    @Override
449    public void onRoleChangedSuccessful(String nick) {
450
451    }
452
453    @Override
454    public void onRoleChangeFailed(String nick, int resId) {
455        displayToast(getString(resId, nick));
456    }
457
458    private void openConversation(Conversation conversation, Bundle extras) {
459        ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
460        final boolean mainNeedsRefresh;
461        if (conversationFragment == null) {
462            mainNeedsRefresh = false;
463            Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
464            if (mainFragment != null && mainFragment instanceof ConversationFragment) {
465                conversationFragment = (ConversationFragment) mainFragment;
466            } else {
467                conversationFragment = new ConversationFragment();
468                FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
469                fragmentTransaction.replace(R.id.main_fragment, conversationFragment);
470                fragmentTransaction.addToBackStack(null);
471                try {
472                    fragmentTransaction.commit();
473                } catch (IllegalStateException e) {
474                    Log.w(Config.LOGTAG, "sate loss while opening conversation", e);
475                    //allowing state loss is probably fine since view intents et all are already stored and a click can probably be 'ignored'
476                    return;
477                }
478            }
479        } else {
480            mainNeedsRefresh = true;
481        }
482        conversationFragment.reInit(conversation, extras == null ? new Bundle() : extras);
483        if (mainNeedsRefresh) {
484            refreshFragment(R.id.main_fragment);
485        } else {
486            invalidateActionBarTitle();
487        }
488    }
489
490    public boolean onXmppUriClicked(Uri uri) {
491        XmppUri xmppUri = new XmppUri(uri);
492        if (xmppUri.isJidValid() && !xmppUri.hasFingerprints()) {
493            final Conversation conversation = xmppConnectionService.findUniqueConversationByJid(xmppUri);
494            if (conversation != null) {
495                openConversation(conversation, null);
496                return true;
497            }
498        }
499        return false;
500    }
501
502    @Override
503    public boolean onOptionsItemSelected(MenuItem item) {
504        if (MenuDoubleTabUtil.shouldIgnoreTap()) {
505            return false;
506        }
507        switch (item.getItemId()) {
508            case android.R.id.home:
509                FragmentManager fm = getFragmentManager();
510                if (fm.getBackStackEntryCount() > 0) {
511                    try {
512                        fm.popBackStack();
513                    } catch (IllegalStateException e) {
514                        Log.w(Config.LOGTAG, "Unable to pop back stack after pressing home button");
515                    }
516                    return true;
517                }
518                break;
519            case R.id.action_scan_qr_code:
520                UriHandlerActivity.scan(this);
521                return true;
522        }
523        return super.onOptionsItemSelected(item);
524    }
525
526    @Override
527    public void onSaveInstanceState(Bundle savedInstanceState) {
528        Intent pendingIntent = pendingViewIntent.peek();
529        savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
530        super.onSaveInstanceState(savedInstanceState);
531    }
532
533    @Override
534    protected void onStart() {
535        final int theme = findTheme();
536        if (this.mTheme != theme) {
537            this.mSkipBackgroundBinding = true;
538            recreate();
539        } else {
540            this.mSkipBackgroundBinding = false;
541        }
542        mRedirectInProcess.set(false);
543        super.onStart();
544    }
545
546    @Override
547    protected void onNewIntent(final Intent intent) {
548        if (isViewOrShareIntent(intent)) {
549            if (xmppConnectionService != null) {
550                processViewIntent(intent);
551            } else {
552                pendingViewIntent.push(intent);
553            }
554        }
555        setIntent(createLauncherIntent(this));
556    }
557
558    @Override
559    public void onPause() {
560        this.mActivityPaused = true;
561        super.onPause();
562    }
563
564    @Override
565    public void onResume() {
566        super.onResume();
567        this.mActivityPaused = false;
568    }
569
570    private void initializeFragments() {
571        FragmentTransaction transaction = getFragmentManager().beginTransaction();
572        Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
573        Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
574        if (mainFragment != null) {
575            if (binding.secondaryFragment != null) {
576                if (mainFragment instanceof ConversationFragment) {
577                    getFragmentManager().popBackStack();
578                    transaction.remove(mainFragment);
579                    transaction.commit();
580                    getFragmentManager().executePendingTransactions();
581                    transaction = getFragmentManager().beginTransaction();
582                    transaction.replace(R.id.secondary_fragment, mainFragment);
583                    transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
584                    transaction.commit();
585                    return;
586                }
587            } else {
588                if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
589                    transaction.remove(secondaryFragment);
590                    transaction.commit();
591                    getFragmentManager().executePendingTransactions();
592                    transaction = getFragmentManager().beginTransaction();
593                    transaction.replace(R.id.main_fragment, secondaryFragment);
594                    transaction.addToBackStack(null);
595                    transaction.commit();
596                    return;
597                }
598            }
599        } else {
600            transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment());
601        }
602        if (binding.secondaryFragment != null && secondaryFragment == null) {
603            transaction.replace(R.id.secondary_fragment, new ConversationFragment());
604        }
605        transaction.commit();
606    }
607
608    private void invalidateActionBarTitle() {
609        final ActionBar actionBar = getSupportActionBar();
610        if (actionBar != null) {
611            Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
612            if (mainFragment != null && mainFragment instanceof ConversationFragment) {
613                final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
614                if (conversation != null) {
615                    actionBar.setTitle(EmojiWrapper.transform(conversation.getName()));
616                    actionBar.setDisplayHomeAsUpEnabled(true);
617                    return;
618                }
619            }
620            actionBar.setTitle(R.string.app_name);
621            actionBar.setDisplayHomeAsUpEnabled(false);
622        }
623    }
624
625    @Override
626    public void onConversationArchived(Conversation conversation) {
627        if (performRedirectIfNecessary(conversation, false)) {
628            return;
629        }
630        Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
631        if (mainFragment != null && mainFragment instanceof ConversationFragment) {
632            try {
633                getFragmentManager().popBackStack();
634            } catch (IllegalStateException e) {
635                Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e);
636                //this usually means activity is no longer active; meaning on the next open we will run through this again
637            }
638            return;
639        }
640        Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
641        if (secondaryFragment != null && secondaryFragment instanceof ConversationFragment) {
642            if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
643                Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
644                if (suggestion != null) {
645                    openConversation(suggestion, null);
646                }
647            }
648        }
649    }
650
651    @Override
652    public void onConversationsListItemUpdated() {
653        Fragment fragment = getFragmentManager().findFragmentById(R.id.main_fragment);
654        if (fragment != null && fragment instanceof ConversationsOverviewFragment) {
655            ((ConversationsOverviewFragment) fragment).refresh();
656        }
657    }
658
659    @Override
660    public void switchToConversation(Conversation conversation) {
661        Log.d(Config.LOGTAG, "override");
662        openConversation(conversation, null);
663    }
664
665    @Override
666    public void onConversationRead(Conversation conversation, String upToUuid) {
667        if (!mActivityPaused && pendingViewIntent.peek() == null) {
668            xmppConnectionService.sendReadMarker(conversation, upToUuid);
669        } else {
670            Log.d(Config.LOGTAG, "ignoring read callback. mActivityPaused=" + Boolean.toString(mActivityPaused));
671        }
672    }
673
674    @Override
675    public void onAccountUpdate() {
676        this.refreshUi();
677    }
678
679    @Override
680    public void onConversationUpdate() {
681        if (performRedirectIfNecessary(false)) {
682            return;
683        }
684        this.refreshUi();
685    }
686
687    @Override
688    public void onRosterUpdate() {
689        this.refreshUi();
690    }
691
692    @Override
693    public void OnUpdateBlocklist(OnUpdateBlocklist.Status status) {
694        this.refreshUi();
695    }
696
697    @Override
698    public void onShowErrorToast(int resId) {
699        runOnUiThread(() -> Toast.makeText(this, resId, Toast.LENGTH_SHORT).show());
700    }
701}