chromium/chrome/android/java/src/org/chromium/chrome/browser/sync/ui/PassphraseActivity.java

// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chrome.browser.sync.ui;

import android.accounts.Account;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;

import org.chromium.chrome.R;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.sync.SyncErrorNotifier;
import org.chromium.chrome.browser.sync.SyncServiceFactory;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.sync.SyncService;

/**
 * This activity is used for requesting a sync passphrase from the user. Typically, this will be the
 * target of an Android notification.
 */
public class PassphraseActivity extends AppCompatActivity
        implements PassphraseDialogFragment.Delegate, FragmentManager.OnBackStackChangedListener {
    public static final String FRAGMENT_PASSPHRASE = "passphrase_fragment";
    public static final String FRAGMENT_SPINNER = "spinner_fragment";

    private Profile mProfile;
    private IdentityManager mIdentityManager;
    private SyncService mSyncService;

    private SyncService.SyncStateChangedListener mSyncStateChangedListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // The Chrome browser process must be started here because this Activity
        // may be started explicitly from Android notifications.
        // During a normal user flow the ChromeTabbedActivity would start the Chrome browser
        // process and this wouldn't be necessary.
        ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
        mProfile = ProfileManager.getLastUsedRegularProfile();
        mIdentityManager = IdentityServicesProvider.get().getIdentityManager(mProfile);
        mSyncService = SyncServiceFactory.getForProfile(mProfile);
        assert mSyncService != null;
        getSupportFragmentManager().addOnBackStackChangedListener(this);
    }

    @Override
    protected void onResume() {
        super.onResume();
        Account account =
                CoreAccountInfo.getAndroidAccountFrom(
                        mIdentityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN));
        if (account == null) {
            finish();
            return;
        }

        if (!isShowingDialog(FRAGMENT_PASSPHRASE)) {
            if (mSyncService.isEngineInitialized()) {
                displayPassphraseDialog();
            } else {
                addSyncStateChangedListener();
                displaySpinnerDialog();
            }
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        // Make sure we don't receive callbacks while in the background.
        // See http://crbug.com/469890.
        removeSyncStateChangedListener();
    }

    private void addSyncStateChangedListener() {
        if (mSyncStateChangedListener != null) {
            return;
        }
        mSyncStateChangedListener =
                new SyncService.SyncStateChangedListener() {
                    @Override
                    public void syncStateChanged() {
                        if (mSyncService.isEngineInitialized()) {
                            removeSyncStateChangedListener();
                            displayPassphraseDialog();
                        }
                    }
                };
        mSyncService.addSyncStateChangedListener(mSyncStateChangedListener);
    }

    private void removeSyncStateChangedListener() {
        if (mSyncStateChangedListener != null) {
            mSyncService.removeSyncStateChangedListener(mSyncStateChangedListener);
            mSyncStateChangedListener = null;
        }
    }

    private boolean isShowingDialog(String tag) {
        return getFragmentManager().findFragmentByTag(tag) != null;
    }

    private void displayPassphraseDialog() {
        assert mSyncService.isEngineInitialized();
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.addToBackStack(null);
        PassphraseDialogFragment.newInstance(null).show(ft, FRAGMENT_PASSPHRASE);
    }

    private void displaySpinnerDialog() {
        FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
        ft.addToBackStack(null);
        SpinnerDialogFragment dialog = new SpinnerDialogFragment();
        dialog.show(ft, FRAGMENT_SPINNER);
    }

    /** Callback for {@link PassphraseDialogFragment.Delegate} */
    @Override
    public boolean onPassphraseEntered(String passphrase) {
        if (!passphrase.isEmpty() && mSyncService.setDecryptionPassphrase(passphrase)) {
            // The passphrase was correct - close this activity.
            finish();
            return true;
        }
        return false;
    }

    @Override
    public void onPassphraseCanceled() {
        // Re add the notification.
        SyncErrorNotifier.getForProfile(mProfile).syncStateChanged();
        finish();
    }

    @Override
    public Profile getProfile() {
        return mProfile;
    }

    @Override
    public void onBackStackChanged() {
        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
            finish();
        }
    }

    /** Dialog shown while sync is loading. */
    public static class SpinnerDialogFragment extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            ProgressDialog dialog = new ProgressDialog(getActivity());
            dialog.setMessage(getResources().getString(R.string.sync_loading));
            return dialog;
        }
    }
}