chromium/chrome/browser/ui/android/device_lock/java/src/org/chromium/chrome/browser/ui/device_lock/MissingDeviceLockLauncher.java

// Copyright 2023 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.ui.device_lock;

import android.app.KeyguardManager;
import android.content.Context;

import androidx.annotation.VisibleForTesting;

import org.chromium.base.Callback;
import org.chromium.base.metrics.RecordHistogram;
import org.chromium.chrome.browser.autofill.PersonalDataManagerFactory;
import org.chromium.chrome.browser.password_manager.PasswordStoreBridge;
import org.chromium.chrome.browser.preferences.ChromePreferenceKeys;
import org.chromium.chrome.browser.preferences.ChromeSharedPreferences;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.signin.services.SigninManager.DataWipeOption;
import org.chromium.components.signin.identitymanager.ConsentLevel;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.signin.metrics.SignoutReason;
import org.chromium.ui.modaldialog.DialogDismissalCause;
import org.chromium.ui.modaldialog.ModalDialogManager;

public class MissingDeviceLockLauncher {
    private Context mContext;
    private Profile mProfile;
    private ModalDialogManager mModalDialogManager;
    private MissingDeviceLockCoordinator mMissingDeviceLockCoordinator;
    private PasswordStoreBridge mPasswordStoreBridge;

    /**
     * Launcher to show and handle the Missing Device Lock dialog to the user to prompt them to
     * recreate a device lock.
     *
     * @param context The context hosting the current activity.
     * @param profile The profile to clear data from.
     * @param modalDialogManager The {@link ModalDialogManager} to host the
     *        Missing Device Lock dialog.
     */
    public MissingDeviceLockLauncher(
            Context context, Profile profile, ModalDialogManager modalDialogManager) {
        mContext = context;
        mProfile = profile;
        mModalDialogManager = modalDialogManager;
        mMissingDeviceLockCoordinator = null;
    }

    /**
     * Prompts the user to recreate a device lock if one is expected but has been removed. If the
     * user chooses to proceed without a device lock, highly sensitive data will be deleted, with
     * the option to delete all personal data.
     *
     * @return The {@link MissingDeviceLockCoordinator} for the Missing Device Lock UI being shown.
     */
    public MissingDeviceLockCoordinator checkPrivateDataIsProtectedByDeviceLock() {
        KeyguardManager keyguardManager =
                (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);

        // If device lock is present, record that to show an alert if it is later removed.
        if (keyguardManager.isDeviceSecure()) {
            // Hide the Missing Device Lock dialog if a device lock has been set (as prompted).
            if (mMissingDeviceLockCoordinator != null) {
                mMissingDeviceLockCoordinator.hideDialog(
                        DialogDismissalCause.ACTION_ON_DIALOG_COMPLETED);
                RecordHistogram.recordEnumeratedHistogram(
                        "Android.Automotive.DeviceLockRemovalDialogEvent",
                        MissingDeviceLockCoordinator.MissingDeviceLockDialogEvent
                                .DEVICE_LOCK_RESTORED,
                        MissingDeviceLockCoordinator.MissingDeviceLockDialogEvent.COUNT);
                mMissingDeviceLockCoordinator = null;
            }
            ChromeSharedPreferences.getInstance()
                    .writeBoolean(ChromePreferenceKeys.DEVICE_LOCK_SHOW_ALERT_IF_REMOVED, true);
            return null;
        }

        // If the device lock has been removed, prompt the user with the missing device lock UI.
        if (mMissingDeviceLockCoordinator == null
                && ChromeSharedPreferences.getInstance()
                        .readBoolean(
                                ChromePreferenceKeys.DEVICE_LOCK_SHOW_ALERT_IF_REMOVED, false)) {
            Callback<Boolean> onContinueWithoutDeviceLock =
                    (wipeAllData) ->
                            ensureSignOutAndDeleteSensitiveData(
                                    () ->
                                            mMissingDeviceLockCoordinator.hideDialog(
                                                    DialogDismissalCause.POSITIVE_BUTTON_CLICKED),
                                    wipeAllData);
            mMissingDeviceLockCoordinator =
                    new MissingDeviceLockCoordinator(
                            onContinueWithoutDeviceLock, mContext, mModalDialogManager);
            mMissingDeviceLockCoordinator.showDialog();
            return mMissingDeviceLockCoordinator;
        }

        return null;
    }

    /**
     * Signs out the user if applicable and deletes sensitive personal data.
     *
     * @param wipeDataCallback Callback to run after the data has been wiped.
     * @param wipeAllData Whether or not to wipe all personal data. If false, only passwords and
     *         credit cards will be deleted.
     */
    void ensureSignOutAndDeleteSensitiveData(Runnable wipeDataCallback, boolean wipeAllData) {
        SigninManager signinManager = IdentityServicesProvider.get().getSigninManager(mProfile);
        IdentityManager identityManager =
                IdentityServicesProvider.get().getIdentityManager(mProfile);

        signinManager.runAfterOperationInProgress(
                () -> {
                    if (identityManager.getPrimaryAccountInfo(ConsentLevel.SIGNIN) != null) {
                        signinManager.signOut(
                                SignoutReason.DEVICE_LOCK_REMOVED_ON_AUTOMOTIVE,
                                () -> {
                                    if (!wipeAllData) {
                                        deletePasswordsAndCreditCards();
                                    }
                                    wipeDataCallback.run();
                                },
                                wipeAllData);
                    } else {
                        if (wipeAllData) {
                            signinManager.wipeSyncUserData(
                                    wipeDataCallback, DataWipeOption.WIPE_ALL_PROFILE_DATA);
                        } else {
                            deletePasswordsAndCreditCards();
                            wipeDataCallback.run();
                        }
                    }
                    ChromeSharedPreferences.getInstance()
                            .writeBoolean(
                                    ChromePreferenceKeys.DEVICE_LOCK_SHOW_ALERT_IF_REMOVED, false);
                });
    }

    void setPasswordStoreBridgeForTesting(PasswordStoreBridge passwordStoreBridge) {
        mPasswordStoreBridge = passwordStoreBridge;
    }

    @VisibleForTesting
    PasswordStoreBridge getPasswordStoreBridge() {
        if (mPasswordStoreBridge == null) {
            mPasswordStoreBridge = new PasswordStoreBridge(mProfile);
        }
        return mPasswordStoreBridge;
    }

    private void deletePasswordsAndCreditCards() {
        getPasswordStoreBridge().clearAllPasswords();
        PersonalDataManagerFactory.getForProfile(mProfile).deleteAllLocalCreditCards();
    }

    /**
     * Set the Missing Device Lock coordinator for testing.
     *
     * @param missingDeviceLockCoordinator The coordinator to use for testing.
     */
    void setMissingDeviceLockCoordinatorForTesting(
            MissingDeviceLockCoordinator missingDeviceLockCoordinator) {
        mMissingDeviceLockCoordinator = missingDeviceLockCoordinator;
    }
}