chromium/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/PasswordSettingsUpdaterDispatcherBridge.java

// Copyright 2022 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.password_manager;

import static org.chromium.base.ThreadUtils.assertOnBackgroundThread;
import static org.chromium.chrome.browser.password_manager.PasswordManagerSetting.AUTO_SIGN_IN;
import static org.chromium.chrome.browser.password_manager.PasswordManagerSetting.BIOMETRIC_REAUTH_BEFORE_PWD_FILLING;
import static org.chromium.chrome.browser.password_manager.PasswordManagerSetting.OFFER_TO_SAVE_PASSWORDS;
import static org.chromium.chrome.browser.password_manager.PasswordSettingsUpdaterMetricsRecorder.getStoreType;

import android.accounts.Account;

import org.jni_zero.CalledByNative;
import org.jni_zero.JNINamespace;

import org.chromium.base.task.PostTask;
import org.chromium.base.task.TaskTraits;
import org.chromium.components.signin.AccountUtils;

import java.util.Optional;

/**
 * Java-counterpart of the native PasswordSettingsUpdaterAndroidDispatcherBridge. It forwards
 * passwords settings updates from native to the downstream implementation. Response callbacks
 * are forwarded by a separate PasswordSettingsUpdaterReceiverBridge.
 */
@JNINamespace("password_manager")
public class PasswordSettingsUpdaterDispatcherBridge {
    private final PasswordSettingsAccessor mSettingsAccessor;
    private final PasswordSettingsUpdaterReceiverBridge mReceiverBridge;

    PasswordSettingsUpdaterDispatcherBridge(
            PasswordSettingsUpdaterReceiverBridge settingsUpdaterReceiverBridge,
            PasswordSettingsAccessor settingsAccessor) {
        assertOnBackgroundThread();
        assert settingsUpdaterReceiverBridge != null;
        assert settingsAccessor != null;
        mReceiverBridge = settingsUpdaterReceiverBridge;
        mSettingsAccessor = settingsAccessor;
    }

    @CalledByNative
    static PasswordSettingsUpdaterDispatcherBridge create(
            PasswordSettingsUpdaterReceiverBridge settingsUpdaterReceiverBridge) {
        return new PasswordSettingsUpdaterDispatcherBridge(
                settingsUpdaterReceiverBridge,
                PasswordSettingsAccessorFactoryImpl.getOrCreate().createAccessor());
    }

    @CalledByNative
    void getSettingValue(String account, @PasswordManagerSetting int setting) {
        assertOnBackgroundThread();
        PasswordSettingsUpdaterMetricsRecorder metricsRecorder =
                new PasswordSettingsUpdaterMetricsRecorder(
                        setting,
                        PasswordSettingsUpdaterMetricsRecorder.GET_VALUE_FUNCTION_SUFFIX,
                        getStoreType(account));
        switch (setting) {
            case OFFER_TO_SAVE_PASSWORDS:
                mSettingsAccessor.getOfferToSavePasswords(
                        getAccount(account),
                        offerToSavePasswords ->
                                mReceiverBridge.onSettingValueFetched(
                                        OFFER_TO_SAVE_PASSWORDS,
                                        offerToSavePasswords,
                                        metricsRecorder),
                        exception ->
                                handleFetchingExceptionOnUiThread(
                                        OFFER_TO_SAVE_PASSWORDS, exception, metricsRecorder));
                break;
            case AUTO_SIGN_IN:
                mSettingsAccessor.getAutoSignIn(
                        getAccount(account),
                        autoSignIn ->
                                mReceiverBridge.onSettingValueFetched(
                                        AUTO_SIGN_IN, autoSignIn, metricsRecorder),
                        exception ->
                                handleFetchingExceptionOnUiThread(
                                        AUTO_SIGN_IN, exception, metricsRecorder));
                break;
            case BIOMETRIC_REAUTH_BEFORE_PWD_FILLING:
                mSettingsAccessor.getUseBiometricReauthBeforeFilling(
                        value ->
                                handleSettingValueFetchedOnUiThread(
                                        BIOMETRIC_REAUTH_BEFORE_PWD_FILLING,
                                        value,
                                        metricsRecorder),
                        exception ->
                                handleFetchingExceptionOnUiThread(
                                        BIOMETRIC_REAUTH_BEFORE_PWD_FILLING,
                                        exception,
                                        metricsRecorder));
                break;
            default:
                assert false : "All settings need to be handled.";
        }
    }

    @CalledByNative
    void setSettingValue(String account, @PasswordManagerSetting int setting, boolean value) {
        assertOnBackgroundThread();
        PasswordSettingsUpdaterMetricsRecorder metricsRecorder =
                new PasswordSettingsUpdaterMetricsRecorder(
                        setting,
                        PasswordSettingsUpdaterMetricsRecorder.SET_VALUE_FUNCTION_SUFFIX,
                        getStoreType(account));
        switch (setting) {
            case OFFER_TO_SAVE_PASSWORDS:
                mSettingsAccessor.setOfferToSavePasswords(
                        value,
                        getAccount(account),
                        unused ->
                                mReceiverBridge.onSettingValueSet(
                                        OFFER_TO_SAVE_PASSWORDS, metricsRecorder),
                        exception ->
                                handleSettingExceptionOnUiThread(
                                        OFFER_TO_SAVE_PASSWORDS, exception, metricsRecorder));
                break;
            case AUTO_SIGN_IN:
                mSettingsAccessor.setAutoSignIn(
                        value,
                        getAccount(account),
                        unused -> mReceiverBridge.onSettingValueSet(AUTO_SIGN_IN, metricsRecorder),
                        exception ->
                                handleSettingExceptionOnUiThread(
                                        AUTO_SIGN_IN, exception, metricsRecorder));
                break;
            default:
                assert false : "All settings need to be handled.";
        }
    }

    // TODO(crbug.com/343879727) : Remove this after "Authenticate with biometrics before password
    // filling" setting is introduced in GMS Core.
    private void handleSettingValueFetchedOnUiThread(
            @PasswordManagerSetting int setting,
            Optional<Boolean> value,
            PasswordSettingsUpdaterMetricsRecorder metricsRecorder) {
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> mReceiverBridge.onSettingValueFetched(setting, value, metricsRecorder));
    }

    private void handleFetchingExceptionOnUiThread(
            @PasswordManagerSetting int setting,
            Exception exception,
            PasswordSettingsUpdaterMetricsRecorder metricsRecorder) {
        // Error callback could be either triggered
        // - by the GMS Core on the UI thread
        // - by the downstream backend on the operation thread if preconditions are not met
        // |runOrPostTask| ensures callback will always be executed on the UI thread.
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> mReceiverBridge.handleFetchingException(setting, exception, metricsRecorder));
    }

    private void handleSettingExceptionOnUiThread(
            @PasswordManagerSetting int setting,
            Exception exception,
            PasswordSettingsUpdaterMetricsRecorder metricsRecorder) {
        // Error callback could be either triggered
        // - by the GMS Core on the UI thread
        // - by the downstream backend on the operation thread if preconditions are not met
        // |runOrPostTask| ensures callback will always be executed on the UI thread.
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> mReceiverBridge.handleSettingException(setting, exception, metricsRecorder));
    }

    private Optional<Account> getAccount(String syncingAccount) {
        if (syncingAccount == null) return Optional.empty();
        return Optional.of(AccountUtils.createAccountFromName(syncingAccount));
    }
}