chromium/chrome/browser/ui/android/webid/internal/java/src/org/chromium/chrome/browser/ui/android/webid/AccountSelectionButtonModeIntegrationTest.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.ui.android.webid;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;

import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import static org.chromium.base.ThreadUtils.runOnUiThreadBlocking;
import static org.chromium.base.test.util.CriteriaHelper.pollUiThread;
import static org.chromium.ui.test.util.MockitoHelper.doCallback;

import android.graphics.Bitmap;
import android.view.View;
import android.widget.TextView;

import androidx.recyclerview.widget.RecyclerView;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.NoMatchingViewException;
import androidx.test.filters.MediumTest;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import org.chromium.base.Callback;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.blink.mojom.RpContext;
import org.chromium.blink.mojom.RpMode;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionMediator.AccountChooserResult;
import org.chromium.chrome.browser.ui.android.webid.AccountSelectionProperties.HeaderProperties.HeaderType;
import org.chromium.chrome.browser.ui.android.webid.data.Account;
import org.chromium.chrome.browser.ui.android.webid.data.IdentityProviderData;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetController;
import org.chromium.components.browser_ui.bottomsheet.BottomSheetTestSupport;
import org.chromium.content.webid.IdentityRequestDialogDismissReason;

import java.util.Arrays;

/**
 * Integration tests for the Account Selection Button Mode component check that the calls to the
 * Account Selection API end up rendering a View. This class is parameterized to run all tests for
 * each RP mode.
 */
@RunWith(ChromeJUnit4ClassRunner.class)
@Batch(Batch.PER_CLASS)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
public class AccountSelectionButtonModeIntegrationTest extends AccountSelectionIntegrationTestBase {
    @Before
    @Override
    public void setUp() throws InterruptedException {
        mRpMode = RpMode.BUTTON;
        super.setUp();
    }

    @Test
    @MediumTest
    public void testAddReturningUserAccount() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        onView(withId(R.id.account_selection_add_account_btn))
                .check(matches(withText("Use a different account")));

        doAnswer(
                        new Answer<Void>() {
                            @Override
                            public Void answer(InvocationOnMock invocation) {
                                mAccountSelection.showAccounts(
                                        EXAMPLE_ETLD_PLUS_ONE,
                                        TEST_ETLD_PLUS_ONE_2,
                                        Arrays.asList(NEW_BOB, RETURNING_ANA),
                                        IDP_METADATA_WITH_ADD_ACCOUNT,
                                        mClientIdMetadata,
                                        /* isAutoReauthn= */ false,
                                        RpContext.SIGN_IN,
                                        /* requestPermission= */ true,
                                        mNewAccountsIdpReturningAna);
                                mAccountSelection.getMediator().setComponentShowTime(-1000);
                                return null;
                            }
                        })
                .when(mMockBridge)
                .onLoginToIdP(any(), any());

        // Click "Use a different account".
        runOnUiThreadBlocking(
                () -> {
                    contentView.findViewById(R.id.account_selection_add_account_btn).performClick();
                });

        // Because of newAccountsIdp and the account is a returning user, user is now signed in and
        // shown the verifying UI.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);

        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testAddNewUserAccount() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        onView(withId(R.id.account_selection_add_account_btn))
                .check(matches(withText("Use a different account")));

        doAnswer(
                        new Answer<Void>() {
                            @Override
                            public Void answer(InvocationOnMock invocation) {
                                mAccountSelection.showAccounts(
                                        EXAMPLE_ETLD_PLUS_ONE,
                                        TEST_ETLD_PLUS_ONE_2,
                                        Arrays.asList(NEW_BOB, RETURNING_ANA),
                                        IDP_METADATA_WITH_ADD_ACCOUNT,
                                        mClientIdMetadata,
                                        /* isAutoReauthn= */ false,
                                        RpContext.SIGN_IN,
                                        /* requestPermission= */ true,
                                        mNewAccountsIdpNewBob);
                                mAccountSelection.getMediator().setComponentShowTime(-1000);
                                return null;
                            }
                        })
                .when(mMockBridge)
                .onLoginToIdP(any(), any());

        // Click "Use a different account".
        runOnUiThreadBlocking(
                () -> {
                    contentView.findViewById(R.id.account_selection_add_account_btn).performClick();
                });

        // Because of newAccountsIdp and the account is a non-returning user which requires us to
        // show disclosure text, the next dialog shown should be the request permission dialog with
        // only the newly signed-in account and the disclosure text shown.
        assertEquals(
                mAccountSelection.getMediator().getHeaderType(), HeaderType.REQUEST_PERMISSION);
        onView(withId(R.id.account_selection_continue_btn))
                .check(matches(withText("Continue as Bob")));
        onView(withId(R.id.user_data_sharing_consent))
                .check(
                        matches(
                                withText(
                                        "To continue, two.com will share your name, email address,"
                                            + " and profile picture with this site. See this site's"
                                            + " privacy policy and terms of service.")));

        // Click "Continue" to proceed to the verifying UI.
        clickContinueButton();
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);

        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testBrowserTrustedLoginStatePrecedesLoginState() {
        Account account =
                new Account(
                        "Test",
                        "[email protected]",
                        "Test",
                        "Test",
                        TEST_PROFILE_PIC,
                        null,
                        /* isSignIn= */ true,
                        /* isBrowserTrustedSignIn= */ false);
        IdentityProviderData newAccountsIdp =
                new IdentityProviderData(
                        EXAMPLE_ETLD_PLUS_ONE,
                        new Account[] {account},
                        IDP_METADATA_WITH_ADD_ACCOUNT,
                        mClientIdMetadata,
                        RpContext.SIGN_IN,
                        /* requestPermission= */ true,
                        /* hasLoginStatusMismatch= */ false);

        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        onView(withId(R.id.account_selection_add_account_btn))
                .check(matches(withText("Use a different account")));

        doAnswer(
                        new Answer<Void>() {
                            @Override
                            public Void answer(InvocationOnMock invocation) {
                                mAccountSelection.showAccounts(
                                        EXAMPLE_ETLD_PLUS_ONE,
                                        TEST_ETLD_PLUS_ONE_2,
                                        Arrays.asList(account, RETURNING_ANA),
                                        IDP_METADATA_WITH_ADD_ACCOUNT,
                                        mClientIdMetadata,
                                        /* isAutoReauthn= */ false,
                                        RpContext.SIGN_IN,
                                        /* requestPermission= */ true,
                                        newAccountsIdp);
                                mAccountSelection.getMediator().setComponentShowTime(-1000);
                                return null;
                            }
                        })
                .when(mMockBridge)
                .onLoginToIdP(any(), any());

        // Click "Use a different account".
        runOnUiThreadBlocking(
                () -> {
                    contentView.findViewById(R.id.account_selection_add_account_btn).performClick();
                });

        // Because of newAccountsIdp and the account's IDP claimed login state does not match the
        // account's browser trusted login state, we show the account chooser UI since browser
        // trusted login state takes precedence. We do not show the request permission UI because
        // the IDP claimed login state tells us to not show the disclosure text.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);
        onView(withId(R.id.account_selection_continue_btn))
                .check(matches(withText("Continue as Test")));

        // Click "Continue" to proceed to the verifying UI.
        clickContinueButton();
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);

        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testAccountChooserWithAddAccountForNewUser() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        // This should be the "multi-account chooser", so clicking an account should go
        // to the disclosure text screen.
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        onView(withId(R.id.account_selection_add_account_btn))
                .check(matches(withText("Use a different account")));

        clickFirstAccountInAccountsList();

        // Sheet should still be open.
        assertNotEquals(BottomSheetController.SheetState.HIDDEN, getBottomSheetState());
        onView(withId(R.id.account_selection_continue_btn))
                .check(matches(withText("Continue as Bob")));

        // Make sure we now show the disclosure text.
        TextView consent = contentView.findViewById(R.id.user_data_sharing_consent);
        if (consent == null) {
            throw new NoMatchingViewException.Builder()
                    .includeViewHierarchy(true)
                    .withRootView(contentView)
                    .build();
        }

        clickContinueButton();

        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testAccountChooserWithAddAccountReturningUser() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        onView(withId(R.id.account_selection_add_account_btn))
                .check(matches(withText("Use a different account")));

        clickFirstAccountInAccountsList();

        // Because this is a returning account, we should immediately sign in now.
        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testAddAccountIsSecondaryButtonForSingleAccount() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Check that only one item is in the accounts list, and the item is an account.
        RecyclerView accountsList = contentView.findViewById(R.id.sheet_item_list);
        assertEquals(accountsList.getChildCount(), 1);
        assertEquals(
                accountsList.getAdapter().getItemViewType(0),
                AccountSelectionProperties.ITEM_TYPE_ACCOUNT);

        // Check that secondary button is displayed, with the appropriate text.
        onView(withId(R.id.account_selection_add_account_btn))
                .check(matches(withText("Use a different account")));
    }

    @Test
    @MediumTest
    public void testAddAccountIsAccountRowForMultipleAccounts() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB, RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Check that three items are in the accounts list, the first two items are accounts and the
        // third/last item is an add account button.
        RecyclerView accountsList = contentView.findViewById(R.id.sheet_item_list);
        assertEquals(accountsList.getChildCount(), 3);
        assertEquals(
                accountsList.getAdapter().getItemViewType(0),
                AccountSelectionProperties.ITEM_TYPE_ACCOUNT);
        assertEquals(
                accountsList.getAdapter().getItemViewType(1),
                AccountSelectionProperties.ITEM_TYPE_ACCOUNT);
        assertEquals(
                accountsList.getAdapter().getItemViewType(2),
                AccountSelectionProperties.ITEM_TYPE_ADD_ACCOUNT);

        // Check that secondary button is NOT displayed.
        onView(withId(R.id.account_selection_add_account_btn)).check(matches(not(isDisplayed())));
    }

    @Test
    @MediumTest
    public void testLoadingDialogBackDismissesAndCallsCallback() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showLoadingDialog(
                            EXAMPLE_ETLD_PLUS_ONE, TEST_ETLD_PLUS_ONE_2, RpContext.SIGN_IN);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        Espresso.pressBack();

        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.BACK_PRESS);
        verify(mMockBridge, never()).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testLoadingDialogSwipeDismissesAndCallsCallback() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showLoadingDialog(
                            EXAMPLE_ETLD_PLUS_ONE, TEST_ETLD_PLUS_ONE_2, RpContext.SIGN_IN);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
        runOnUiThreadBlocking(
                () -> {
                    sheetSupport.suppressSheet(BottomSheetController.StateChangeReason.SWIPE);
                });
        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.SWIPE);
        verify(mMockBridge, never()).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testNewUserFlow() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Click the first account in the account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);
        clickFirstAccountInAccountsList();

        // Click continue in the request permission dialog.
        assertEquals(
                mAccountSelection.getMediator().getHeaderType(), HeaderType.REQUEST_PERMISSION);
        clickContinueButton();

        // User is now signed in and shown the verifying UI.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);
        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testReturningUserFlow() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Click the first account account in the account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);
        clickFirstAccountInAccountsList();

        // Because this is a returning account, user is now signed in and shown the verifying UI.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);
        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testRpInApprovedClientsFlow() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ false,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Click the first account account in the account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);
        clickFirstAccountInAccountsList();

        // Because requestPermission is false, user is now signed in and shown the verifying UI.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);
        verify(mMockBridge, never()).onDismissed(anyInt());
        verify(mMockBridge).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testRequestPermissionDialogBackShowsAccountChooser() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Dialog is initially an account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);

        // Clicking an account should show the request permission dialog.
        clickFirstAccountInAccountsList();
        assertEquals(
                mAccountSelection.getMediator().getHeaderType(), HeaderType.REQUEST_PERMISSION);

        // Press back from the request permission dialog, returning to the account chooser.
        Espresso.pressBack();
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);
    }

    @Test
    @MediumTest
    public void testRequestPermissionDialogSwipeDismissesAndCallsCallback() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Dialog is initially an account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);

        // Clicking an account should show the request permission dialog.
        clickFirstAccountInAccountsList();
        assertEquals(
                mAccountSelection.getMediator().getHeaderType(), HeaderType.REQUEST_PERMISSION);

        // Swipe to dismiss on request permission dialog.
        BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
        runOnUiThreadBlocking(
                () -> {
                    sheetSupport.suppressSheet(BottomSheetController.StateChangeReason.SWIPE);
                });
        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.SWIPE);
        verify(mMockBridge, never()).onAccountSelected(any(), any());
    }

    @Test
    @MediumTest
    public void testAccountChooserToVerifyingDialogHeaderReused() {
        doCallback(
                        /* index= */ 1,
                        (Callback<Bitmap> callback) -> {
                            callback.onResult(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888));
                        })
                .when(mMockImageFetcher)
                .fetchImage(any(), any());

        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Dialog is initially an account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);
        String expectedTitle =
                ((TextView) contentView.findViewById(R.id.header_title)).getText().toString();
        String expectedSubtitle =
                ((TextView) contentView.findViewById(R.id.header_subtitle)).getText().toString();
        onView(withId(R.id.header_idp_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.header_rp_icon)).check(matches(not(isDisplayed())));
        onView(withId(R.id.arrow_range_icon)).check(matches(not(isDisplayed())));

        // Clicking an account should show the verifying dialog.
        clickFirstAccountInAccountsList();
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);
        assertEquals(
                expectedTitle,
                ((TextView) contentView.findViewById(R.id.header_title)).getText().toString());
        assertEquals(
                expectedSubtitle,
                ((TextView) contentView.findViewById(R.id.header_subtitle)).getText().toString());
        onView(withId(R.id.header_idp_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.header_rp_icon)).check(matches(not(isDisplayed())));
        onView(withId(R.id.arrow_range_icon)).check(matches(not(isDisplayed())));
    }

    @Test
    @MediumTest
    public void testRequestPermissionDialogToVerifyingDialogHeaderReused() {
        doCallback(
                        /* index= */ 1,
                        (Callback<Bitmap> callback) -> {
                            callback.onResult(Bitmap.createBitmap(40, 40, Bitmap.Config.ARGB_8888));
                        })
                .when(mMockImageFetcher)
                .fetchImage(any(), any());

        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);
        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        // Dialog is initially an account chooser.
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.SIGN_IN);

        // Clicking an account should show the request permission dialog.
        clickFirstAccountInAccountsList();
        assertEquals(
                mAccountSelection.getMediator().getHeaderType(), HeaderType.REQUEST_PERMISSION);
        String expectedTitle =
                ((TextView) contentView.findViewById(R.id.header_title)).getText().toString();
        String expectedSubtitle =
                ((TextView) contentView.findViewById(R.id.header_subtitle)).getText().toString();
        onView(withId(R.id.header_idp_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.header_rp_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.arrow_range_icon)).check(matches(isDisplayed()));

        // Clicking continue should show the verifying dialog.
        clickContinueButton();
        assertEquals(mAccountSelection.getMediator().getHeaderType(), HeaderType.VERIFY);
        assertEquals(
                expectedTitle,
                ((TextView) contentView.findViewById(R.id.header_title)).getText().toString());
        assertEquals(
                expectedSubtitle,
                ((TextView) contentView.findViewById(R.id.header_subtitle)).getText().toString());
        onView(withId(R.id.header_idp_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.header_rp_icon)).check(matches(isDisplayed()));
        onView(withId(R.id.arrow_range_icon)).check(matches(isDisplayed()));
    }

    @Test
    @MediumTest
    public void testAccountSelectionRecordsAccountChooserResultHistogram() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ false,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Blink.FedCm.Button.AccountChooserResult",
                        AccountChooserResult.ACCOUNT_ROW);

        clickFirstAccountInAccountsList();

        histogramWatcher.assertExpected();
    }

    @Test
    @MediumTest
    public void testAddAccountRecordsAccountChooserResultHistogram() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        View contentView = mBottomSheetController.getCurrentSheetContent().getContentView();
        assertNotNull(contentView);

        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Blink.FedCm.Button.AccountChooserResult",
                        AccountChooserResult.USE_OTHER_ACCOUNT_BUTTON);

        // Click "Use a different account".
        runOnUiThreadBlocking(
                () -> {
                    contentView.findViewById(R.id.account_selection_add_account_btn).performClick();
                });

        histogramWatcher.assertExpected();
    }

    @Test
    @MediumTest
    public void testSwipeRecordsAccountChooserResultHistogram() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA, NEW_BOB),
                            IDP_METADATA,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Blink.FedCm.Button.AccountChooserResult", AccountChooserResult.SWIPE);

        BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
        runOnUiThreadBlocking(
                () -> {
                    sheetSupport.suppressSheet(BottomSheetController.StateChangeReason.SWIPE);
                });
        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.SWIPE);

        histogramWatcher.assertExpected();
    }

    @Test
    @MediumTest
    public void testPressBackAccountChooserResultHistogram() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Blink.FedCm.Button.AccountChooserResult", AccountChooserResult.BACK_PRESS);

        Espresso.pressBack();
        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.BACK_PRESS);

        histogramWatcher.assertExpected();
    }

    @Test
    @MediumTest
    public void testTapScrimAccountChooserResultHistogram() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Blink.FedCm.Button.AccountChooserResult", AccountChooserResult.TAP_SCRIM);

        BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
        runOnUiThreadBlocking(
                () -> {
                    sheetSupport.forceClickOutsideTheSheet();
                });

        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.TAP_SCRIM);

        histogramWatcher.assertExpected();
    }

    @Test
    @MediumTest
    public void testTabClosedAccountChooserResultHistogram() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(RETURNING_ANA),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        HistogramWatcher histogramWatcher =
                HistogramWatcher.newSingleRecordWatcher(
                        "Blink.FedCm.Button.AccountChooserResult", AccountChooserResult.TAB_CLOSED);

        BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
        runOnUiThreadBlocking(
                () -> {
                    sheetSupport.suppressSheet(BottomSheetController.StateChangeReason.NAVIGATION);
                });

        waitForEvent(mMockBridge).onDismissed(IdentityRequestDialogDismissReason.OTHER);

        histogramWatcher.assertExpected();
    }

    @Test
    @MediumTest
    public void testMaximizeSheet() {
        runOnUiThreadBlocking(
                () -> {
                    mAccountSelection.showAccounts(
                            EXAMPLE_ETLD_PLUS_ONE,
                            TEST_ETLD_PLUS_ONE_2,
                            Arrays.asList(NEW_BOB),
                            IDP_METADATA_WITH_ADD_ACCOUNT,
                            mClientIdMetadata,
                            /* isAutoReauthn= */ false,
                            RpContext.SIGN_IN,
                            /* requestPermission= */ true,
                            /* newAccountsIdp= */ null);
                    mAccountSelection.getMediator().setComponentShowTime(-1000);
                });
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.HALF);

        BottomSheetTestSupport sheetSupport = new BottomSheetTestSupport(mBottomSheetController);
        runOnUiThreadBlocking(
                () -> sheetSupport.setSheetState(BottomSheetController.SheetState.FULL, false));
        pollUiThread(() -> getBottomSheetState() == BottomSheetController.SheetState.FULL);
    }

    private void clickFirstAccountInAccountsList() {
        runOnUiThreadBlocking(
                () -> {
                    ((RecyclerView)
                                    mBottomSheetController
                                            .getCurrentSheetContent()
                                            .getContentView()
                                            .findViewById(R.id.sheet_item_list))
                            .getChildAt(0)
                            .performClick();
                });
    }

    private void clickContinueButton() {
        runOnUiThreadBlocking(
                () -> {
                    mBottomSheetController
                            .getCurrentSheetContent()
                            .getContentView()
                            .findViewById(R.id.account_selection_continue_btn)
                            .performClick();
                });
    }
}