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