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

// Copyright 2021 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 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.chrome.browser.password_manager.PasswordStoreAndroidBackendReceiverBridgeImpl.JobId;
import org.chromium.components.signin.AccountUtils;

import java.util.Optional;

/**
 * Java-counterpart of the native PasswordStoreAndroidBackendDispatcherBridgeImpl. It's part of the
 * password store backend that forwards password store operations to a downstream implementation.
 */
@JNINamespace("password_manager")
class PasswordStoreAndroidBackendDispatcherBridgeImpl {
    private final PasswordStoreAndroidBackend mBackend;
    private final PasswordStoreAndroidBackendReceiverBridgeImpl mBackendReceiverBridge;

    PasswordStoreAndroidBackendDispatcherBridgeImpl(
            PasswordStoreAndroidBackendReceiverBridgeImpl backendReceiverBridge,
            PasswordStoreAndroidBackend backend) {
        mBackendReceiverBridge = backendReceiverBridge;
        mBackend = backend;
        assert mBackend != null;
    }

    @CalledByNative
    static PasswordStoreAndroidBackendDispatcherBridgeImpl create(
            PasswordStoreAndroidBackendReceiverBridgeImpl backendReceiverBridge) {
        return new PasswordStoreAndroidBackendDispatcherBridgeImpl(
                backendReceiverBridge,
                PasswordStoreAndroidBackendFactory.getInstance().createBackend());
    }

    @CalledByNative
    void getAllLogins(@JobId int jobId, String syncingAccount) {
        mBackend.getAllLogins(
                getAccount(syncingAccount),
                passwords -> mBackendReceiverBridge.onCompleteWithLogins(jobId, passwords),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void getAllLoginsWithBrandingInfo(@JobId int jobId, String syncingAccount) {
        mBackend.getAllLoginsWithBrandingInfo(
                getAccount(syncingAccount),
                passwords -> mBackendReceiverBridge.onCompleteWithBrandedLogins(jobId, passwords),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void getAutofillableLogins(@JobId int jobId, String syncingAccount) {
        mBackend.getAutofillableLogins(
                getAccount(syncingAccount),
                passwords -> mBackendReceiverBridge.onCompleteWithLogins(jobId, passwords),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void getLoginsForSignonRealm(@JobId int jobId, String signonRealm, String syncingAccount) {
        mBackend.getLoginsForSignonRealm(
                signonRealm,
                getAccount(syncingAccount),
                passwords -> mBackendReceiverBridge.onCompleteWithLogins(jobId, passwords),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void getAffiliatedLoginsForSignonRealm(
            @JobId int jobId, String signonRealm, String syncingAccount) {
        mBackend.getAffiliatedLoginsForSignonRealm(
                signonRealm,
                getAccount(syncingAccount),
                passwords ->
                        mBackendReceiverBridge.onCompleteWithAffiliatedLogins(jobId, passwords),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void addLogin(@JobId int jobId, byte[] pwdWithLocalData, String syncingAccount) {
        mBackend.addLogin(
                pwdWithLocalData,
                getAccount(syncingAccount),
                () -> mBackendReceiverBridge.onLoginChanged(jobId),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void updateLogin(@JobId int jobId, byte[] pwdWithLocalData, String syncingAccount) {
        mBackend.updateLogin(
                pwdWithLocalData,
                getAccount(syncingAccount),
                () -> mBackendReceiverBridge.onLoginChanged(jobId),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    @CalledByNative
    void removeLogin(@JobId int jobId, byte[] pwdSpecificsData, String syncingAccount) {
        mBackend.removeLogin(
                pwdSpecificsData,
                getAccount(syncingAccount),
                () -> mBackendReceiverBridge.onLoginChanged(jobId),
                exception -> handleAndroidBackendExceptionOnUiThread(jobId, exception));
    }

    private void handleAndroidBackendExceptionOnUiThread(@JobId int jobId, Exception exception) {
        // Error callback could be either triggered
        // - by the GMS Core on the UI thread
        // - by the password store downstream backend on the operation thread
        // |runOrPostTask| ensures callback will always be executed on the UI thread.
        PostTask.runOrPostTask(
                TaskTraits.UI_DEFAULT,
                () -> mBackendReceiverBridge.handleAndroidBackendException(jobId, exception));
    }

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