chromium/chrome/browser/password_manager/android/java/src/org/chromium/chrome/browser/password_manager/settings/PasswordManagerHandlerProvider.java

// Copyright 2017 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.settings;

import org.chromium.base.ObserverList;
import org.chromium.base.ThreadUtils;
import org.chromium.base.lifetime.Destroyable;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.profiles.ProfileKeyedMap;

/**
 * A provider for PasswordManagerHandler implementations, handling the choice of the proper one
 * (production vs. testing), its lifetime and multiple observers.
 *
 * <p>This class is used by the code responsible for Chrome's passwords settings. There can only be
 * one instance of Chrome's passwords settings opened at a time (although more clients of
 * PasswordManagerHandler can live as nested settings pages).
 */
public class PasswordManagerHandlerProvider implements PasswordListObserver, Destroyable {
    private static ProfileKeyedMap<PasswordManagerHandlerProvider> sProfileMap;

    /** Return the {@link PasswordManagerHandlerProvider} for the given {@link Profile}. */
    public static PasswordManagerHandlerProvider getForProfile(Profile profile) {
        if (sProfileMap == null) {
            sProfileMap =
                    ProfileKeyedMap.createMapOfDestroyables(
                            ProfileKeyedMap.ProfileSelection.REDIRECTED_TO_ORIGINAL);
        }
        return sProfileMap.getForProfile(profile, PasswordManagerHandlerProvider::new);
    }

    private final Profile mProfile;

    // The production implementation of PasswordManagerHandler is |sPasswordUIView|, instantiated on
    // demand. Tests might want to override that by providing a fake implementation through
    // setPasswordManagerHandlerForTest, which is then kept in |mTestPasswordManagerHandler|.
    private PasswordUIView mPasswordUIView;
    private PasswordManagerHandler mTestPasswordManagerHandler;

    // This class is itself a PasswordListObserver, listening directly to a PasswordManagerHandler
    // implementation. But it also keeps a list of other observers, to which it forwards the events.
    private final ObserverList<PasswordListObserver> mObservers =
            new ObserverList<PasswordListObserver>();

    private PasswordManagerHandlerProvider(Profile profile) {
        mProfile = profile;
    }

    /**
     * Sets a testing implementation of PasswordManagerHandler to be used. It overrides the
     * production one even if it exists. The caller needs to ensure that |this| becomes an observer
     * of |passwordManagerHandler|. Also, this must not be called when there are already some
     * observers in |mObservers|, because of special handling of the production implementation of
     * PasswordManagerHandler on removing the last observer.
     */
    public void setPasswordManagerHandlerForTest(PasswordManagerHandler passwordManagerHandler) {
        ThreadUtils.assertOnUiThread();
        assert mObservers.isEmpty();
        mTestPasswordManagerHandler = passwordManagerHandler;
    }

    /**
     * Resets the testing implementation of PasswordManagerHandler, clears all observers and ensures
     * that the view is cleaned up properly.
     */
    public void resetPasswordManagerHandlerForTest() {
        ThreadUtils.assertOnUiThread();
        mObservers.clear();
        mTestPasswordManagerHandler = null;
        if (mPasswordUIView != null) {
            mPasswordUIView.destroy();
            mPasswordUIView = null;
        }
    }

    @Override
    public void destroy() {
        if (mPasswordUIView != null) {
            mPasswordUIView.destroy();
            mPasswordUIView = null;
        }
    }

    /**
     * A convenience function to choose between the production and test PasswordManagerHandler
     * implementation.
     */
    public PasswordManagerHandler getPasswordManagerHandler() {
        ThreadUtils.assertOnUiThread();
        if (mTestPasswordManagerHandler != null) return mTestPasswordManagerHandler;
        return mPasswordUIView;
    }

    /**
     * This method creates the production implementation of PasswordManagerHandler and saves it into
     * mPasswordUIView.
     */
    private void createPasswordManagerHandler() {
        ThreadUtils.assertOnUiThread();
        assert mPasswordUIView == null;
        mPasswordUIView = new PasswordUIView(this, mProfile);
    }

    /** Starts forwarding events from the PasswordManagerHandler implementation to |observer|. */
    public void addObserver(PasswordListObserver observer) {
        ThreadUtils.assertOnUiThread();
        if (getPasswordManagerHandler() == null) createPasswordManagerHandler();
        mObservers.addObserver(observer);
    }

    public void removeObserver(PasswordListObserver observer) {
        ThreadUtils.assertOnUiThread();
        mObservers.removeObserver(observer);
        // If this was the last observer of the production implementation of PasswordManagerHandler,
        // call destroy on it to close the connection to the native C++ code.
        if (mObservers.isEmpty() && mTestPasswordManagerHandler == null) {
            mPasswordUIView.destroy();
            mPasswordUIView = null;
        }
    }

    @Override
    public void passwordListAvailable(int count) {
        ThreadUtils.assertOnUiThread();
        for (PasswordListObserver observer : mObservers) {
            observer.passwordListAvailable(count);
        }
    }

    @Override
    public void passwordExceptionListAvailable(int count) {
        ThreadUtils.assertOnUiThread();
        for (PasswordListObserver observer : mObservers) {
            observer.passwordExceptionListAvailable(count);
        }
    }
}