chromium/chrome/android/javatests/src/org/chromium/chrome/browser/password_manager/settings/PasswordSettingsTestHelper.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.settings;

import android.os.Bundle;
import android.view.View;

import androidx.preference.PreferenceViewHolder;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import androidx.test.espresso.matcher.BoundedMatcher;

import org.hamcrest.Description;
import org.hamcrest.Matcher;

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.ApplicationTestUtils;
import org.chromium.chrome.browser.init.ChromeBrowserInitializer;
import org.chromium.chrome.browser.password_manager.FakePasswordManagerHandler;
import org.chromium.chrome.browser.password_manager.ManagePasswordsReferrer;
import org.chromium.chrome.browser.password_manager.PasswordManagerHelper;
import org.chromium.chrome.browser.profiles.ProfileManager;
import org.chromium.chrome.browser.settings.SettingsActivity;
import org.chromium.chrome.browser.settings.SettingsActivityTestRule;

import java.util.ArrayList;
import java.util.Arrays;

/** Helper functions used in various password settings test suites. */
class PasswordSettingsTestHelper {
    static final SavedPasswordEntry ZEUS_ON_EARTH =
            new SavedPasswordEntry("http://www.phoenicia.gr", "Zeus", "Europa");
    static final SavedPasswordEntry ARES_AT_OLYMP =
            new SavedPasswordEntry("https://1-of-12.olymp.gr", "Ares", "God-o'w@r");
    static final SavedPasswordEntry PHOBOS_AT_OLYMP =
            new SavedPasswordEntry("https://visitor.olymp.gr", "Phobos-son-of-ares", "G0d0fF34r");
    static final SavedPasswordEntry DEIMOS_AT_OLYMP =
            new SavedPasswordEntry("https://visitor.olymp.gr", "Deimops-Ares-son", "G0d0fT3rr0r");
    static final SavedPasswordEntry HADES_AT_UNDERWORLD =
            new SavedPasswordEntry("https://underworld.gr", "", "C3rb3rus");
    static final SavedPasswordEntry[] GREEK_GODS = {
        ZEUS_ON_EARTH, ARES_AT_OLYMP, PHOBOS_AT_OLYMP, DEIMOS_AT_OLYMP, HADES_AT_UNDERWORLD,
    };

    private SettingsActivity mActivityToCleanUp;

    /**
     * Used to provide fake lists of stored passwords. Tests which need it can use setPasswordSource
     */
    private FakePasswordManagerHandler mHandler;

    /**
     * Delayer controlling hiding the progress bar during exporting passwords. This replaces a time
     * delay used in production.
     */
    private final ManualCallbackDelayer mManualDelayer = new ManualCallbackDelayer();

    void tearDown() {
        try {
            ApplicationTestUtils.finishActivity(mActivityToCleanUp);
        } catch (Exception e) {
            // Activity was already finished by test framework. Any exception is not test-related.
        }
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (!ProfileManager.isInitialized()) return;
                    PasswordManagerHandlerProvider.getForProfile(
                                    ProfileManager.getLastUsedRegularProfile())
                            .resetPasswordManagerHandlerForTest();
                });
        setPasswordSource(null);
    }

    ManualCallbackDelayer getManualDelayer() {
        return mManualDelayer;
    }

    FakePasswordManagerHandler getHandler() {
        return mHandler;
    }

    /**
     * Helper to set up a fake source of displayed passwords.
     *
     * @param entry An entry to be added to saved passwords. Can be null.
     */
    void setPasswordSource(SavedPasswordEntry entry) {
        SavedPasswordEntry[] entries = {};
        if (entry != null) {
            entries = new SavedPasswordEntry[] {entry};
        }
        setPasswordSourceWithMultipleEntries(entries);
    }

    /**
     * Helper to set up a fake source of displayed passwords with multiple initial passwords.
     *
     * @param initialEntries All entries to be added to saved passwords. Can not be null.
     */
    void setPasswordSourceWithMultipleEntries(SavedPasswordEntry[] initialEntries) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (!ChromeBrowserInitializer.getInstance().isFullBrowserInitialized()) {
                        ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
                    }
                });

        PasswordManagerHandlerProvider handlerProvider =
                ThreadUtils.runOnUiThreadBlocking(
                        () ->
                                PasswordManagerHandlerProvider.getForProfile(
                                        ProfileManager.getLastUsedRegularProfile()));
        if (mHandler == null) {
            mHandler = new FakePasswordManagerHandler(handlerProvider);
        }
        ArrayList<SavedPasswordEntry> entries = new ArrayList<>(Arrays.asList(initialEntries));
        mHandler.setSavedPasswords(entries);
        ThreadUtils.runOnUiThreadBlocking(
                () -> handlerProvider.setPasswordManagerHandlerForTest(mHandler));
    }

    /**
     * Helper to set up a fake source of displayed passwords without passwords but with exceptions.
     *
     * @param exceptions All exceptions to be added to saved exceptions. Can not be null.
     */
    void setPasswordExceptions(String[] exceptions) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (!ChromeBrowserInitializer.getInstance().isFullBrowserInitialized()) {
                        ChromeBrowserInitializer.getInstance().handleSynchronousStartup();
                    }
                });

        PasswordManagerHandlerProvider handlerProvider =
                ThreadUtils.runOnUiThreadBlocking(
                        () ->
                                PasswordManagerHandlerProvider.getForProfile(
                                        ProfileManager.getLastUsedRegularProfile()));
        if (mHandler == null) {
            mHandler = new FakePasswordManagerHandler(handlerProvider);
        }
        mHandler.setSavedPasswordExceptions(new ArrayList<>(Arrays.asList(exceptions)));
        ThreadUtils.runOnUiThreadBlocking(
                () -> handlerProvider.setPasswordManagerHandlerForTest(mHandler));
    }

    SettingsActivity startPasswordSettingsFromMainSettings(
            SettingsActivityTestRule<PasswordSettings> testRule) {
        Bundle fragmentArgs = new Bundle();
        fragmentArgs.putInt(
                PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER,
                ManagePasswordsReferrer.CHROME_SETTINGS);
        mActivityToCleanUp = testRule.startSettingsActivity(fragmentArgs);
        return mActivityToCleanUp;
    }

    SettingsActivity startPasswordSettingsDirectly(
            SettingsActivityTestRule<PasswordSettings> testRule) {
        Bundle fragmentArgs = new Bundle();
        // The passwords accessory sheet is one of the places that can launch password settings
        // directly (without passing through main settings).
        fragmentArgs.putInt(
                PasswordManagerHelper.MANAGE_PASSWORDS_REFERRER,
                ManagePasswordsReferrer.PASSWORDS_ACCESSORY_SHEET);
        mActivityToCleanUp = testRule.startSettingsActivity(fragmentArgs);
        return mActivityToCleanUp;
    }

    /**
     * Returns a matcher that checks whether a given view contains another view with the given text.
     *
     * @param text The text that the view holder has in its view hierarchy.
     * @return A Matcher to find a particular {@link ViewHolder} that contains certain text.
     */
    static Matcher<ViewHolder> hasTextInViewHolder(String text) {
        return new BoundedMatcher<ViewHolder, PreferenceViewHolder>(PreferenceViewHolder.class) {
            @Override
            public void describeTo(Description description) {
                description.appendText("has text: " + text);
            }

            @Override
            protected boolean matchesSafely(PreferenceViewHolder preferenceViewHolder) {
                ArrayList<View> outViews = new ArrayList<>();
                preferenceViewHolder.itemView.findViewsWithText(
                        outViews, text, View.FIND_VIEWS_WITH_TEXT);
                return !outViews.isEmpty();
            }
        };
    }
}