chromium/chrome/browser/password_manager/android/pwd_check_wrapper/java/src/org/chromium/chrome/browser/pwd_check_wrapper/GmsCorePasswordCheckController.java

// Copyright 2024 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.pwd_check_wrapper;

import static org.chromium.chrome.browser.password_manager.PasswordManagerUtilBridge.usesSplitStoresAndUPMForLocal;

import org.chromium.chrome.browser.password_manager.PasswordCheckReferrer;
import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
import org.chromium.chrome.browser.password_manager.PasswordStoreBridge;
import org.chromium.chrome.browser.password_manager.PasswordStoreCredential;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.sync.SyncService;

import java.lang.ref.WeakReference;
import java.util.concurrent.CompletableFuture;

/**
 * The implementation of {@link PasswordCheckController} which calls the Gms core API to perform the
 * password check and get breached credentials number.
 */
class GmsCorePasswordCheckController
        implements PasswordCheckController, PasswordStoreBridge.PasswordStoreObserver {
    private final SyncService mSyncService;
    private final PrefService mPrefService;
    private final PasswordStoreBridge mPasswordStoreBridge;
    private final PasswordManagerHelper mPasswordManagerHelper;
    private final CompletableFuture<Integer> mPasswordsCountAccountStorage;
    private final CompletableFuture<Integer> mPasswordsCountLocalStorage;

    GmsCorePasswordCheckController(
            SyncService syncService,
            PrefService prefService,
            PasswordStoreBridge passwordStoreBridge,
            PasswordManagerHelper passwordManagerHelper) {
        mSyncService = syncService;
        mPrefService = prefService;
        mPasswordStoreBridge = passwordStoreBridge;
        mPasswordManagerHelper = passwordManagerHelper;
        mPasswordsCountAccountStorage = new CompletableFuture<>();
        mPasswordsCountLocalStorage = new CompletableFuture<>();
        mPasswordStoreBridge.addObserver(this, true);
    }

    @Override
    public CompletableFuture<PasswordCheckResult> checkPasswords(
            @PasswordStorageType int passwordStorageType) {
        WeakReference<GmsCorePasswordCheckController> weakRef = new WeakReference(this);
        CompletableFuture<PasswordCheckResult> passwordCheckResult = new CompletableFuture<>();
        mPasswordManagerHelper.runPasswordCheckupInBackground(
                PasswordCheckReferrer.SAFETY_CHECK,
                PasswordCheckController.getAccountNameForPasswordStorageType(
                        passwordStorageType, mSyncService),
                unused -> {
                    GmsCorePasswordCheckController controller = weakRef.get();
                    if (controller == null) return;

                    controller.getBreachedCredentialsCount(
                            passwordStorageType, passwordCheckResult);
                },
                error -> {
                    GmsCorePasswordCheckController controller = weakRef.get();
                    if (controller == null) return;

                    controller.onPasswordCheckFailed(error, passwordCheckResult);
                });
        return passwordCheckResult;
    }

    @Override
    public CompletableFuture<PasswordCheckResult> getBreachedCredentialsCount(
            @PasswordStorageType int passwordStorageType) {
        return getBreachedCredentialsCount(passwordStorageType, new CompletableFuture<>());
    }

    private CompletableFuture<PasswordCheckResult> getBreachedCredentialsCount(
            @PasswordStorageType int passwordStorageType,
            CompletableFuture<PasswordCheckResult> passwordCheckResult) {
        WeakReference<GmsCorePasswordCheckController> weakRef = new WeakReference(this);
        mPasswordManagerHelper.getBreachedCredentialsCount(
                PasswordCheckReferrer.SAFETY_CHECK,
                PasswordCheckController.getAccountNameForPasswordStorageType(
                        passwordStorageType, mSyncService),
                count -> {
                    GmsCorePasswordCheckController controller = weakRef.get();
                    if (controller == null) return;

                    controller.onBreachedCredentialsObtained(
                            passwordStorageType, count, passwordCheckResult);
                },
                error -> {
                    GmsCorePasswordCheckController controller = weakRef.get();
                    if (controller == null) return;
                    controller.onPasswordCheckFailed(error, passwordCheckResult);
                });
        return passwordCheckResult;
    }

    @Override
    public void destroy() {
        mPasswordStoreBridge.removeObserver(this);
        mPasswordStoreBridge.destroy();
    }

    /**
     * Combines the result of the password check and passwords loading from the store and provides
     * the password check result.
     *
     * @param breachedCount the number of breached credentials
     */
    private void onBreachedCredentialsObtained(
            @PasswordStorageType int passwordStorageType,
            int breachedCount,
            CompletableFuture<PasswordCheckResult> passwordCheckResult) {
        CompletableFuture<Integer> passwordsTotalCount =
                passwordStorageType == PasswordStorageType.ACCOUNT_STORAGE
                        ? mPasswordsCountAccountStorage
                        : mPasswordsCountLocalStorage;
        // If passwordsTotalCount is already completed, the code after `thenAccept` is executed
        // immediately.
        passwordsTotalCount.thenAccept(
                totalCount ->
                        passwordCheckResult.complete(
                                new PasswordCheckResult(totalCount, breachedCount)));
    }

    private void onPasswordCheckFailed(
            Exception error, CompletableFuture<PasswordCheckResult> passwordCheckResult) {
        passwordCheckResult.complete(new PasswordCheckResult(error));
    }

    @Override
    public void onSavedPasswordsChanged(int count) {
        // The count here is the total count for both account and local storage. If this method is
        // called, this means that the passwords for both account and profile stores have been
        // fetched and can be requested.
        mPasswordStoreBridge.removeObserver(this);

        // If using split stores and UPM for local passwords is enabled, the local passwords are
        // stored in the profile store.
        if (usesSplitStoresAndUPMForLocal(mPrefService)) {
            mPasswordsCountAccountStorage.complete(
                    mPasswordStoreBridge.getPasswordStoreCredentialsCountForAccountStore());
            mPasswordsCountLocalStorage.complete(
                    mPasswordStoreBridge.getPasswordStoreCredentialsCountForProfileStore());
            return;
        }
        // If using split stores is disabled, all passwords reside in the profile store.
        mPasswordsCountAccountStorage.complete(
                mPasswordStoreBridge.getPasswordStoreCredentialsCountForProfileStore());
        mPasswordsCountLocalStorage.complete(0);
    }

    /** Not relevant for this controller. */
    @Override
    public void onEdit(PasswordStoreCredential credential) {}
}