chromium/chrome/android/features/tab_ui/javatests/src/org/chromium/chrome/browser/tasks/tab_management/SelectableTabListEditorTest.java

// Copyright 2019 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.tasks.tab_management;

import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;

import static androidx.test.espresso.intent.Intents.intended;
import static androidx.test.espresso.intent.matcher.BundleMatchers.hasEntry;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasAction;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasExtras;
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasType;

import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.isNotNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_LOW_END_DEVICE;
import static org.chromium.base.test.util.Restriction.RESTRICTION_TYPE_NON_LOW_END_DEVICE;
import static org.chromium.chrome.browser.flags.ChromeFeatureList.TAB_GROUP_PARITY_ANDROID;
import static org.chromium.ui.test.util.MockitoHelper.doCallback;

import android.content.Intent;
import android.os.Build.VERSION_CODES;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.test.espresso.Espresso;
import androidx.test.espresso.intent.Intents;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;

import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import org.chromium.base.Callback;
import org.chromium.base.GarbageCollectionTestUtils;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisableIf;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.RequiresRestart;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.app.bookmarks.BookmarkEditActivity;
import org.chromium.chrome.browser.bookmarks.BookmarkModel;
import org.chromium.chrome.browser.bookmarks.BookmarkUtils;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabLaunchType;
import org.chromium.chrome.browser.tab.TabSelectionType;
import org.chromium.chrome.browser.tab_ui.RecyclerViewPosition;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.tasks.tab_groups.TabGroupModelFilter;
import org.chromium.chrome.browser.tasks.tab_management.ActionConfirmationManager.ConfirmationResult;
import org.chromium.chrome.browser.tasks.tab_management.TabListEditorAction.ButtonType;
import org.chromium.chrome.browser.tasks.tab_management.TabListEditorAction.IconPosition;
import org.chromium.chrome.browser.tasks.tab_management.TabListEditorAction.ShowMode;
import org.chromium.chrome.browser.tasks.tab_management.TabProperties.TabActionState;
import org.chromium.chrome.browser.ui.messages.snackbar.Snackbar;
import org.chromium.chrome.browser.ui.messages.snackbar.SnackbarManager;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.ChromeTabbedActivityTestRule;
import org.chromium.chrome.test.R;
import org.chromium.chrome.test.batch.BlankCTATabInitialStateRule;
import org.chromium.chrome.test.util.BookmarkTestUtil;
import org.chromium.chrome.test.util.ChromeRenderTestRule;
import org.chromium.chrome.test.util.ChromeTabUtils;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.modaldialog.ModalDialogManager;
import org.chromium.ui.test.util.UiRestriction;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/** End-to-end test for the selectable TabListEditor. */
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({
    ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
    "force-fieldtrials=Study/Group",
    "force-fieldtrial-params=Study.Group:enable_launch_polish/true"
})
@DisableFeatures({TAB_GROUP_PARITY_ANDROID})
@Batch(Batch.PER_CLASS)
public class SelectableTabListEditorTest {
    private static final String TAB_GROUP_LAUNCH_POLISH_PARAMS =
            "force-fieldtrial-params=Study.Group:enable_launch_polish/true";
    private static final String PAGE_WITH_HTTPS_CANONICAL_URL =
            "/chrome/test/data/android/share/link_share_https_canonical.html";
    private static final String PAGE_WITH_HTTP_CANONICAL_URL =
            "/chrome/test/data/android/share/link_share_http_canonical.html";
    private static final String PAGE_WITH_NO_CANONICAL_URL =
            "/chrome/test/data/android/share/link_share_no_canonical.html";

    @ClassRule
    public static ChromeTabbedActivityTestRule sActivityTestRule =
            new ChromeTabbedActivityTestRule();

    @Rule
    public BlankCTATabInitialStateRule mBlankCTATabInitialStateRule =
            new BlankCTATabInitialStateRule(sActivityTestRule, false);

    @Rule
    public ChromeRenderTestRule mRenderTestRule =
            ChromeRenderTestRule.Builder.withPublicCorpus()
                    .setBugComponent(ChromeRenderTestRule.Component.UI_BROWSER_MOBILE_TAB_SWITCHER)
                    .setRevision(9)
                    .setDescription("Favicon update.")
                    .build();

    @Mock private Callback<RecyclerViewPosition> mSetRecyclerViewPosition;
    @Mock private ModalDialogManager mModalDialogManager;
    @Mock private ActionConfirmationManager mActionConfirmationManager;

    private TabListEditorTestingRobot mRobot = new TabListEditorTestingRobot();

    private TabModelSelector mTabModelSelector;
    private TabListEditorCoordinator.TabListEditorController mTabListEditorController;
    private TabListEditorLayout mTabListEditorLayout;
    private TabListEditorCoordinator mTabListEditorCoordinator;
    private WeakReference<TabListEditorLayout> mRef;
    private ViewGroup mParentView;
    private SnackbarManager mSnackbarManager;
    private BookmarkModel mBookmarkModel;
    private TabGroupCreationDialogManager mCreationDialogManager;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        ChromeTabbedActivity cta = sActivityTestRule.getActivity();
        // Eagerly inflate the tab switcher.

        boolean isTabSwitcherReady =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return cta.getTabSwitcherSupplierForTesting().get() != null;
                        });
        if (!isTabSwitcherReady) {
            TabUiTestHelper.enterTabSwitcher(cta);
            TabUiTestHelper.leaveTabSwitcher(cta);
        }

        mTabModelSelector = cta.getTabModelSelector();
        mParentView = cta.findViewById(R.id.coordinator);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mCreationDialogManager =
                            new TabGroupCreationDialogManager(
                                    cta,
                                    cta.getModalDialogManager(),
                                    /* onTabGroupCreation= */ null);
                    ViewGroup compositorViewHolder = cta.getCompositorViewHolderForTesting();
                    ViewGroup rootView =
                            DeviceFormFactor.isNonMultiDisplayContextOnTablet(cta)
                                    ? (ViewGroup) cta.findViewById(R.id.tab_switcher_view_holder)
                                    : compositorViewHolder;
                    mSnackbarManager = new SnackbarManager(cta, rootView, null);
                    var currentTabModelFilterSupplier =
                            mTabModelSelector
                                    .getTabModelFilterProvider()
                                    .getCurrentTabModelFilterSupplier();
                    mTabListEditorCoordinator =
                            new TabListEditorCoordinator(
                                    cta,
                                    mParentView,
                                    mParentView,
                                    cta.getBrowserControlsManager(),
                                    currentTabModelFilterSupplier,
                                    cta.getTabContentManager(),
                                    mSetRecyclerViewPosition,
                                    getMode(),
                                    /* displayGroups= */ true,
                                    mSnackbarManager,
                                    /* bottomSheetController= */ null,
                                    TabProperties.TabActionState.SELECTABLE,
                                    /* gridCardOnClickListenerProvider= */ null,
                                    mModalDialogManager);

                    mTabListEditorController = mTabListEditorCoordinator.getController();
                    mTabListEditorLayout =
                            mTabListEditorCoordinator.getTabListEditorLayoutForTesting();
                    mRef = new WeakReference<>(mTabListEditorLayout);
                    mBookmarkModel = cta.getBookmarkModelForTesting();
                });

        // The inner most callback may check pref service in C++, needs to be on the UI thread.
        Callback<Callback<Integer>> immediateContinue =
                (Callback<Integer> callback) ->
                        ThreadUtils.runOnUiThreadBlocking(
                                () -> callback.onResult(ConfirmationResult.IMMEDIATE_CONTINUE));
        doCallback(1, immediateContinue)
                .when(mActionConfirmationManager)
                .processCloseTabAttempt(any(), any());
    }

    @After
    public void tearDown() {
        if (mTabListEditorCoordinator != null) {
            if (sActivityTestRule.getActivity().findViewById(R.id.app_menu_list) != null) {
                Espresso.pressBack();
            }

            ThreadUtils.runOnUiThreadBlocking(
                    () -> {
                        if (mTabListEditorController.isVisible()) {
                            mTabListEditorController.hide();
                        }
                        mTabListEditorCoordinator.destroy();
                    });

            if (sActivityTestRule
                    .getActivity()
                    .getLayoutManager()
                    .isLayoutVisible(LayoutType.TAB_SWITCHER)) {
                TabUiTestHelper.leaveTabSwitcher(sActivityTestRule.getActivity());
            }
        }
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    if (mSnackbarManager == null) return;
                    mSnackbarManager.dismissAllSnackbars();
                });
        BookmarkUtils.clearLastUsedPrefs();
    }

    private @TabListCoordinator.TabListMode int getMode() {
        return SysUtils.isLowEndDevice()
                ? TabListCoordinator.TabListMode.LIST
                : TabListCoordinator.TabListMode.GRID;
    }

    private void prepareBlankTab(int num, boolean isIncognito) {
        for (int i = 0; i < num - 1; i++) {
            ChromeTabUtils.newTabFromMenu(
                    InstrumentationRegistry.getInstrumentation(),
                    sActivityTestRule.getActivity(),
                    isIncognito,
                    true);
            sActivityTestRule.loadUrl("about:blank");
        }
    }

    private void createNewTab(@TabLaunchType int launchType, boolean isIncognito) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    sActivityTestRule
                            .getActivity()
                            .getTabCreator(isIncognito)
                            .createNewTab(new LoadUrlParams("about:blank"), launchType, null);
                });
    }

    private void prepareBlankTabWithThumbnail(int num, boolean isIncognito) {
        if (isIncognito) {
            TabUiTestHelper.prepareTabsWithThumbnail(sActivityTestRule, 0, num, "about:blank");
        } else {
            TabUiTestHelper.prepareTabsWithThumbnail(sActivityTestRule, num, 0, "about:blank");
        }
    }

    private void prepareBlankTabGroup(int num, boolean isIncognito) {
        ArrayList<String> urls = new ArrayList<>(Collections.nCopies(num, "about:blank"));

        prepareTabGroupWithUrls(urls, isIncognito);
    }

    private void prepareTabGroupWithUrls(ArrayList<String> urls, boolean isIncognito) {
        for (String url : urls) {
            ChromeTabUtils.newTabFromMenu(
                    InstrumentationRegistry.getInstrumentation(),
                    sActivityTestRule.getActivity(),
                    isIncognito,
                    true);
            sActivityTestRule.loadUrl(url);
        }
        if (urls.size() == 1) return;

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    ArrayList<Tab> tabs = new ArrayList<>();
                    TabModel model = mTabModelSelector.getCurrentModel();
                    TabGroupModelFilter filter =
                            (TabGroupModelFilter)
                                    mTabModelSelector
                                            .getTabModelFilterProvider()
                                            .getCurrentTabModelFilter();
                    for (int i = model.getCount() - urls.size(); i < model.getCount(); i++) {
                        tabs.add(model.getTabAt(i));
                    }
                    // Don't notify to avoid snackbar appearing.
                    filter.mergeListOfTabsToGroup(
                            tabs.subList(1, tabs.size()), tabs.get(0), /* notify= */ false);
                });
    }

    @Test
    @MediumTest
    public void testShowTabs() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.MENU_ONLY,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));
                    showSelectionEditor(tabs, actions);
                });

        mRobot.resultRobot
                .verifyTabListEditorIsVisible()
                .verifyToolbarActionViewDisabled(R.id.tab_list_editor_group_menu_item)
                .verifyToolbarActionViewWithText(R.id.tab_list_editor_group_menu_item, "Group tabs")
                .verifyToolbarSelectionTextWithResourceId(
                        R.string.tab_selection_editor_toolbar_select_tabs)
                .verifyAdapterHasItemCount(tabs.size())
                .verifyHasAtLeastNItemVisible(1);
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot.verifyToolbarMenuItemState("Close tabs", /* enabled= */ false);
        Espresso.pressBack();
    }

    @Test
    @MediumTest
    public void testToggleItem() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        showSelectionEditor(tabs, null);

        mRobot.resultRobot.verifyItemNotSelectedAtAdapterPosition(0);

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot
                .verifyItemSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionText("1 tab");

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot
                .verifyItemNotSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionTextWithResourceId(
                        R.string.tab_selection_editor_toolbar_select_tabs);
    }

    @Test
    @MediumTest
    public void testSelectItemsThroughActionButton() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        showSelectionEditor(tabs, null);

        mRobot.resultRobot.verifyItemNotSelectedAtAdapterPosition(0);

        mRobot.actionRobot.clickActionButtonAdapterPosition(0, getActionButtonId());
        mRobot.resultRobot
                .verifyItemSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionText("1 tab");

        mRobot.actionRobot.clickActionButtonAdapterPosition(1, getActionButtonId());
        mRobot.resultRobot
                .verifyItemSelectedAtAdapterPosition(1)
                .verifyToolbarSelectionText("2 tabs");

        mRobot.actionRobot.clickActionButtonAdapterPosition(1, getActionButtonId());
        mRobot.resultRobot
                .verifyItemNotSelectedAtAdapterPosition(1)
                .verifyToolbarSelectionText("1 tab");

        mRobot.actionRobot.clickActionButtonAdapterPosition(0, getActionButtonId());
        mRobot.resultRobot
                .verifyItemNotSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionTextWithResourceId(
                        R.string.tab_selection_editor_toolbar_select_tabs);
    }

    @Test
    @MediumTest
    public void testToolbarNavigationButtonHideTabListEditor() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        showSelectionEditor(tabs, null);

        mRobot.resultRobot.verifyTabListEditorIsVisible();

        mRobot.actionRobot.clickToolbarNavigationButton();
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        verify(mSetRecyclerViewPosition, times(1)).onResult(isNotNull());
    }

    @Test
    @MediumTest
    public void testHideOnNewTab() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();
        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        createNewTab(TabLaunchType.FROM_STARTUP, false);
        mRobot.resultRobot.verifyTabListEditorIsHidden();

        tabs = getTabsInCurrentTabModel();
        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        createNewTab(TabLaunchType.FROM_RESTORE, false);
        mRobot.resultRobot.verifyTabListEditorIsHidden();
    }

    @Test
    @MediumTest
    public void testToolbarGroupButtonEnabledState() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));
                    showSelectionEditor(tabs, actions);
                });

        mRobot.resultRobot.verifyToolbarActionViewDisabled(R.id.tab_list_editor_group_menu_item);

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot.verifyToolbarActionViewEnabled(R.id.tab_list_editor_group_menu_item);

        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.resultRobot.verifyToolbarActionViewEnabled(R.id.tab_list_editor_group_menu_item);

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot.verifyToolbarActionViewEnabled(R.id.tab_list_editor_group_menu_item);

        mRobot.actionRobot.clickItemAtAdapterPosition(1);
        mRobot.resultRobot.verifyToolbarActionViewDisabled(R.id.tab_list_editor_group_menu_item);
    }

    @Test
    @MediumTest
    public void testUndoToolbarGroup() {
        ChromeTabbedActivity cta = sActivityTestRule.getActivity();
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();
        TabUiTestHelper.enterTabSwitcher(cta);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));
                    showSelectionEditor(tabs, actions);
                });

        mRobot.resultRobot.verifyToolbarActionViewDisabled(R.id.tab_list_editor_group_menu_item);

        mRobot.actionRobot
                .clickItemAtAdapterPosition(0)
                .clickItemAtAdapterPosition(1)
                .clickToolbarActionView(R.id.tab_list_editor_group_menu_item);

        mRobot.resultRobot.verifyTabListEditorIsHidden();
        TabUiTestHelper.verifyTabSwitcherCardCount(cta, 1);

        CriteriaHelper.pollInstrumentationThread(TabUiTestHelper::verifyUndoBarShowingAndClickUndo);
        TabUiTestHelper.verifyTabSwitcherCardCount(cta, 2);
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    public void testConfigureToolbarMenuItems() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.MENU_ONLY,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        final int closeId = R.id.tab_list_editor_close_menu_item;
        mRobot.resultRobot
                .verifyToolbarActionViewDisabled(closeId)
                .verifyToolbarActionViewWithText(closeId, "Close tabs");
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot.verifyToolbarMenuItemState("Group tabs", /* enabled= */ false);
        Espresso.pressBack();

        for (int i = 0; i < tabs.size(); i++) {
            mRobot.actionRobot.clickItemAtAdapterPosition(i);
        }
        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(closeId)
                .verifyToolbarActionViewWithText(closeId, "Close tabs");
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot.verifyToolbarMenuItemState("Group tabs", /* enabled= */ true);
        Espresso.pressBack();

        for (int i = 0; i < tabs.size(); i++) {
            mRobot.actionRobot.clickItemAtAdapterPosition(i);
        }
        mRobot.resultRobot
                .verifyToolbarActionViewDisabled(closeId)
                .verifyToolbarActionViewWithText(closeId, "Close tabs");
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot.verifyToolbarMenuItemState("Group tabs", /* enabled= */ false);
        Espresso.pressBack();
    }

    @Test
    @MediumTest
    public void testToolbarMenuItem_CloseActionView() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));
                    showSelectionEditor(tabs, actions);
                });

        final int closeId = R.id.tab_list_editor_close_menu_item;
        mRobot.resultRobot.verifyToolbarActionViewDisabled(closeId);

        mRobot.actionRobot.clickItemAtAdapterPosition(0).clickToolbarActionView(closeId);

        mRobot.resultRobot.verifyTabListEditorIsHidden();
        verify(mSetRecyclerViewPosition, times(2)).onResult(isNotNull());

        assertEquals(1, getTabsInCurrentTabModel().size());
    }

    @Test
    @MediumTest
    public void testToolbarMenuItem_CloseActionView_WithGroups() {
        prepareBlankTab(2, false);
        prepareBlankTabGroup(3, false);
        prepareBlankTabGroup(1, false);
        prepareBlankTabGroup(2, false);
        List<Tab> tabs = getTabsInCurrentTabModelFilter();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));

                    showSelectionEditor(tabs, actions);
                });

        final int closeId = R.id.tab_list_editor_close_menu_item;
        mRobot.resultRobot.verifyToolbarActionViewDisabled(closeId);

        mRobot.actionRobot
                .clickItemAtAdapterPosition(0)
                .clickItemAtAdapterPosition(2)
                .clickItemAtAdapterPosition(3);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(closeId)
                .verifyToolbarSelectionText("5 tabs");

        View close = mTabListEditorLayout.getToolbar().findViewById(closeId);
        assertEquals("Close 5 selected tabs", close.getContentDescription());

        mRobot.actionRobot.clickToolbarActionView(closeId);

        assertEquals(3, getTabsInCurrentTabModel().size());
    }

    // Regression test for https://crbug.com/1374935
    @Test
    @MediumTest
    public void testToolbarMenuItem_GroupActionView_WithGroups() {
        prepareBlankTab(2, false); // Index: 0, 1
        prepareBlankTabGroup(3, false); // Index: 2
        prepareBlankTabGroup(1, false); // Index: 3
        prepareBlankTabGroup(2, false); // Index: 4
        List<Tab> tabs = getTabsInCurrentTabModelFilter();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        final int groupId = R.id.tab_list_editor_group_menu_item;
        mRobot.resultRobot.verifyToolbarActionViewDisabled(groupId);

        mRobot.actionRobot
                .clickItemAtAdapterPosition(4)
                .clickItemAtAdapterPosition(3)
                .clickItemAtAdapterPosition(1)
                .clickItemAtAdapterPosition(0);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(groupId)
                .verifyToolbarSelectionText("5 tabs");

        View close = mTabListEditorLayout.getToolbar().findViewById(groupId);
        assertEquals("Group 5 selected tabs", close.getContentDescription());

        mRobot.actionRobot.clickToolbarActionView(groupId);

        assertEquals(2, getTabsInCurrentTabModelFilter().size());
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @DisabledTest(message = "crbug.com/1511804")
    public void testToolbarMenuItem_GroupActionAndUndo() throws Exception {
        prepareBlankTabWithThumbnail(2, false);
        prepareBlankTabGroup(3, false);
        prepareBlankTabGroup(1, false);
        prepareBlankTabGroup(2, false);
        TabUiTestHelper.createTabsWithThumbnail(sActivityTestRule, 1, "about:blank", false);
        List<Tab> tabs = getTabsInCurrentTabModelFilter();
        List<Tab> beforeTabOrder = getTabsInCurrentTabModel();

        Tab selectedTab = beforeTabOrder.get(4);
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabModelSelector.getCurrentModel().setIndex(4, TabSelectionType.FROM_USER);
                });
        assertEquals(selectedTab, mTabModelSelector.getCurrentTab());

        // Enter tab switcher to get all thumbnails.
        TabUiTestHelper.enterTabSwitcher(sActivityTestRule.getActivity());
        TabUiTestHelper.verifyAllTabsHaveThumbnail(
                sActivityTestRule.getActivity().getCurrentTabModel());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        final int groupId = R.id.tab_list_editor_group_menu_item;
        mRobot.resultRobot.verifyToolbarActionViewDisabled(groupId);

        mRobot.actionRobot
                .clickItemAtAdapterPosition(0)
                .clickItemAtAdapterPosition(1)
                .clickItemAtAdapterPosition(2)
                .clickItemAtAdapterPosition(3)
                .clickItemAtAdapterPosition(4)
                .clickItemAtAdapterPosition(5);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(groupId)
                .verifyToolbarSelectionText("9 tabs");

        View group = mTabListEditorLayout.getToolbar().findViewById(groupId);
        assertEquals("Group 9 selected tabs", group.getContentDescription());

        // Force the position to something fixed to 100% avoid flakes here.
        TabListRecyclerView tabListRecyclerView =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            TabListRecyclerView recyclerView =
                                    mTabListEditorLayout.findViewById(R.id.tab_list_recycler_view);
                            recyclerView.scrollToPosition(4);
                            return recyclerView;
                        });

        TabUiTestHelper.waitForThumbnailsToFetch(tabListRecyclerView);
        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "groups_before_undo_scrolled");

        mRobot.actionRobot.clickToolbarActionView(groupId);

        mRobot.resultRobot.verifyTabListEditorIsHidden();
        TabUiTestHelper.verifyTabSwitcherCardCount(sActivityTestRule.getActivity(), 1);

        CriteriaHelper.pollInstrumentationThread(TabUiTestHelper::verifyUndoBarShowingAndClickUndo);
        TabUiTestHelper.verifyTabSwitcherCardCount(sActivityTestRule.getActivity(), 6);

        assertEquals(selectedTab, mTabModelSelector.getCurrentTab());
        List<Tab> finalTabs = getTabsInCurrentTabModel();
        assertEquals(beforeTabOrder.size(), finalTabs.size());
        assertEquals(beforeTabOrder, finalTabs);
        List<Tab> finalRootTabs = getTabsInCurrentTabModelFilter();
        assertEquals(tabs.size(), finalRootTabs.size());
        assertEquals(tabs, finalRootTabs);
    }

    @Test
    @MediumTest
    public void testToolbarMenuItem_CloseMenuItem() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.MENU_ONLY,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot.clickToolbarMenuButton().clickToolbarMenuItem("Close tabs");
        Espresso.pressBack();

        assertEquals(2, getTabsInCurrentTabModel().size());

        mRobot.actionRobot
                .clickItemAtAdapterPosition(0)
                .clickToolbarMenuButton()
                .clickToolbarMenuItem("Close tab");

        assertEquals(1, getTabsInCurrentTabModel().size());
    }

    @Test
    @MediumTest
    @RequiresRestart("Share sheet is sometimes persistent when calling pressBack to retract")
    public void testToolbarMenuItem_ShareActionView() {
        Intents.init();
        prepareBlankTab(1, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        final String httpsCanonicalUrl =
                sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
        sActivityTestRule.loadUrl(httpsCanonicalUrl);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorShareAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int shareId = R.id.tab_list_editor_share_menu_item;
        mRobot.resultRobot.verifyToolbarActionViewWithText(shareId, "Share tabs");
        mRobot.resultRobot.verifyToolbarActionViewDisabled(shareId);

        mRobot.actionRobot.clickItemAtAdapterPosition(0);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(shareId)
                .verifyToolbarSelectionText("1 tab");

        View share = mTabListEditorLayout.getToolbar().findViewById(shareId);
        assertEquals("Share 1 selected tab", share.getContentDescription());

        mRobot.actionRobot.clickToolbarActionView(shareId);

        CriteriaHelper.pollUiThread(
                () ->
                        Criteria.checkThat(
                                "Share sheet was not shown.",
                                sActivityTestRule
                                        .getActivity()
                                        .getRootUiCoordinatorForTesting()
                                        .getBottomSheetController(),
                                notNullValue()));

        intended(
                allOf(
                        hasAction(equalTo(Intent.ACTION_CHOOSER)),
                        hasExtras(
                                hasEntry(
                                        equalTo(Intent.EXTRA_INTENT),
                                        allOf(
                                                hasAction(equalTo(Intent.ACTION_SEND)),
                                                hasType("text/plain"))))));
        Intents.release();
    }

    @Test
    @MediumTest
    @RequiresRestart("Share sheet is sometimes persistent when calling pressBack to retract")
    public void testToolbarMenuItem_ShareActionTabsOnly() {
        prepareBlankTab(3, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        final String httpsCanonicalUrl =
                sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
        sActivityTestRule.loadUrl(httpsCanonicalUrl);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorShareAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot.clickItemAtAdapterPosition(0).clickItemAtAdapterPosition(2);

        TabListEditorShareAction.setIntentCallbackForTesting(
                (result -> {
                    assertEquals(Intent.ACTION_SEND, result.getAction());
                    assertEquals(httpsCanonicalUrl, result.getStringExtra(Intent.EXTRA_TEXT));
                    assertEquals("text/plain", result.getType());
                    assertEquals("1 link from Chrome", result.getStringExtra(Intent.EXTRA_TITLE));
                }));

        final int shareId = R.id.tab_list_editor_share_menu_item;
        mRobot.actionRobot.clickToolbarActionView(shareId);
    }

    @Test
    @MediumTest
    @RequiresRestart("Share sheet is sometimes persistent when calling pressBack to retract")
    public void testToolbarMenuItem_ShareActionGroupsOnly() {
        ArrayList<String> urls = new ArrayList<>();
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL));
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTP_CANONICAL_URL));
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_NO_CANONICAL_URL));
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_NO_CANONICAL_URL));

        prepareTabGroupWithUrls(urls, false);
        prepareBlankTabGroup(2, false);

        List<Tab> tabs = getTabsInCurrentTabModelFilter();

        // Url string formatting
        for (int i = 0; i < urls.size(); i++) {
            urls.set(i, (i + 1) + ". " + urls.get(i));
        }
        urls.add("");

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorShareAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot.clickItemAtAdapterPosition(1).clickItemAtAdapterPosition(2);

        TabListEditorShareAction.setIntentCallbackForTesting(
                (result -> {
                    assertEquals(Intent.ACTION_SEND, result.getAction());
                    assertEquals(String.join("\n", urls), result.getStringExtra(Intent.EXTRA_TEXT));
                    assertEquals("text/plain", result.getType());
                    assertEquals("4 links from Chrome", result.getStringExtra(Intent.EXTRA_TITLE));
                }));

        final int shareId = R.id.tab_list_editor_share_menu_item;
        mRobot.actionRobot.clickToolbarActionView(shareId);
    }

    @Test
    @MediumTest
    @RequiresRestart("Share sheet is sometimes persistent when calling pressBack to retract")
    public void testToolbarMenuItem_ShareActionTabsWithGroups() {
        prepareBlankTab(2, false);

        final String httpsCanonicalUrl =
                sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
        sActivityTestRule.loadUrl(httpsCanonicalUrl);

        prepareBlankTabGroup(2, false);

        ArrayList<String> urls = new ArrayList<>();
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTP_CANONICAL_URL));
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_NO_CANONICAL_URL));
        prepareTabGroupWithUrls(urls, false);

        List<Tab> tabs = getTabsInCurrentTabModelFilter();

        // Url string formatting
        urls.add(0, httpsCanonicalUrl);
        for (int i = 0; i < urls.size(); i++) {
            urls.set(i, (i + 1) + ". " + urls.get(i));
        }
        urls.add("");

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorShareAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot
                .clickItemAtAdapterPosition(0)
                .clickItemAtAdapterPosition(1)
                .clickItemAtAdapterPosition(2)
                .clickItemAtAdapterPosition(3);

        TabListEditorShareAction.setIntentCallbackForTesting(
                (result -> {
                    assertEquals(Intent.ACTION_SEND, result.getAction());
                    assertEquals(String.join("\n", urls), result.getStringExtra(Intent.EXTRA_TEXT));
                    assertEquals("text/plain", result.getType());
                    assertEquals("3 links from Chrome", result.getStringExtra(Intent.EXTRA_TITLE));
                }));

        final int shareId = R.id.tab_list_editor_share_menu_item;
        mRobot.actionRobot.clickToolbarActionView(shareId);
    }

    @Test
    @MediumTest
    public void testToolbarMenuItem_ShareActionAllFilterableTabs() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorShareAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int shareId = R.id.tab_list_editor_share_menu_item;
        mRobot.actionRobot.clickItemAtAdapterPosition(0).clickItemAtAdapterPosition(1);
        mRobot.resultRobot
                .verifyToolbarActionViewDisabled(shareId)
                .verifyToolbarSelectionText("2 tabs");
    }

    @Test
    @MediumTest
    @DisableIf.Build(sdk_is_greater_than = VERSION_CODES.R, message = "crbug.com/1511804")
    public void testToolbarMenuItem_BookmarkActionSingleTab() {
        prepareBlankTab(1, false);

        final String httpsCanonicalUrl =
                sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
        sActivityTestRule.loadUrl(httpsCanonicalUrl);

        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorBookmarkAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int bookmarkId = R.id.tab_list_editor_bookmark_menu_item;
        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.actionRobot.clickToolbarActionView(bookmarkId);

        BookmarkTestUtil.waitForBookmarkModelLoaded();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    assertTrue(
                            mBookmarkModel.doesBookmarkExist(
                                    mBookmarkModel.getUserBookmarkIdForTab(tabs.get(0))));

                    Snackbar currentSnackbar = mSnackbarManager.getCurrentSnackbarForTesting();
                    assertEquals(
                            Snackbar.UMA_BOOKMARK_ADDED, currentSnackbar.getIdentifierForTesting());
                    assertEquals(
                            "Bookmarked to Mobile bookmarks", currentSnackbar.getTextForTesting());
                    currentSnackbar.getController().onAction(null);
                });
        BookmarkEditActivity activity = BookmarkTestUtil.waitForEditActivity();
        activity.finish();

        mRobot.resultRobot.verifyTabListEditorIsVisible();
    }

    @Test
    @MediumTest
    public void testToolbarMenuItem_BookmarkActionGroupsOnly() {
        prepareBlankTabGroup(2, false);
        List<Tab> tabs = getTabsInCurrentTabModelFilter();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorBookmarkAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int bookmarkId = R.id.tab_list_editor_bookmark_menu_item;
        mRobot.actionRobot.clickItemAtAdapterPosition(0).clickItemAtAdapterPosition(1);
        mRobot.actionRobot.clickToolbarActionView(bookmarkId);

        BookmarkTestUtil.waitForBookmarkModelLoaded();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    for (Tab tab : tabs) {
                        assertTrue(
                                mBookmarkModel.doesBookmarkExist(
                                        mBookmarkModel.getUserBookmarkIdForTab(tab)));
                    }
                    Snackbar currentSnackbar = mSnackbarManager.getCurrentSnackbarForTesting();
                    assertEquals(
                            Snackbar.UMA_BOOKMARK_ADDED, currentSnackbar.getIdentifierForTesting());
                    assertEquals("Bookmarked", currentSnackbar.getTextForTesting());
                });

        mRobot.resultRobot.verifyTabListEditorIsVisible();
        ThreadUtils.runOnUiThreadBlocking(() -> mTabListEditorController.handleBackPressed());
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        Snackbar currentSnackbar = mSnackbarManager.getCurrentSnackbarForTesting();
        assertEquals("Bookmarked", currentSnackbar.getTextForTesting());
        ThreadUtils.runOnUiThreadBlocking(
                () ->
                        mSnackbarManager.dismissSnackbars(
                                mSnackbarManager.getCurrentSnackbarForTesting().getController()));
    }

    @Test
    @MediumTest
    public void testToolbarMenuItem_BookmarkActionTabsWithGroups() {
        final String httpsCanonicalUrl =
                sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTPS_CANONICAL_URL);
        sActivityTestRule.loadUrl(httpsCanonicalUrl);

        ArrayList<String> urls = new ArrayList<>();
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_HTTP_CANONICAL_URL));
        urls.add(sActivityTestRule.getTestServer().getURL(PAGE_WITH_NO_CANONICAL_URL));

        prepareTabGroupWithUrls(urls, false);
        List<Tab> tabs = getTabsInCurrentTabModelFilter();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorBookmarkAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int bookmarkId = R.id.tab_list_editor_bookmark_menu_item;
        mRobot.resultRobot.verifyToolbarActionViewWithText(bookmarkId, "Bookmark tabs");
        mRobot.resultRobot.verifyToolbarActionViewDisabled(bookmarkId);

        mRobot.actionRobot.clickItemAtAdapterPosition(0).clickItemAtAdapterPosition(1);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(bookmarkId)
                .verifyToolbarSelectionText("3 tabs");

        View bookmark = mTabListEditorLayout.getToolbar().findViewById(bookmarkId);
        assertEquals("Bookmark 3 selected tabs", bookmark.getContentDescription());

        mRobot.actionRobot.clickToolbarActionView(bookmarkId);

        BookmarkTestUtil.waitForBookmarkModelLoaded();
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    Snackbar currentSnackbar = mSnackbarManager.getCurrentSnackbarForTesting();
                    assertEquals(
                            Snackbar.UMA_BOOKMARK_ADDED, currentSnackbar.getIdentifierForTesting());
                    assertEquals("Bookmarked", currentSnackbar.getTextForTesting());
                    currentSnackbar.getController().onAction(null);
                });

        BookmarkEditActivity activity = BookmarkTestUtil.waitForEditActivity();
        activity.finish();

        mRobot.resultRobot.verifyTabListEditorIsVisible();
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    public void testSelectionAction_IndividualTabSelection() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorSelectionAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int selectionId = R.id.tab_list_editor_selection_menu_item;
        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Select all");

        mRobot.actionRobot.clickItemAtAdapterPosition(0);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Select all");

        mRobot.actionRobot.clickItemAtAdapterPosition(1);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Deselect all");

        mRobot.actionRobot.clickItemAtAdapterPosition(0);

        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Select all");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
    public void testGridViewAppearance() throws IOException {
        prepareBlankTabWithThumbnail(3, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        // Enter tab switcher to get all thumbnails.
        TabUiTestHelper.enterTabSwitcher(sActivityTestRule.getActivity());
        TabUiTestHelper.verifyAllTabsHaveThumbnail(
                sActivityTestRule.getActivity().getCurrentTabModel());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.resultRobot.verifyTabListEditorIsVisible();

        TabListRecyclerView tabListRecyclerView =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return mTabListEditorLayout.findViewById(R.id.tab_list_recycler_view);
                        });
        TabUiTestHelper.waitForThumbnailsToFetch(tabListRecyclerView);

        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "grid_view_0.85");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
    public void testGridViewAppearance_oneSelectedTab() throws IOException {
        prepareBlankTabWithThumbnail(3, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        // Enter tab switcher to get all thumbnails.
        TabUiTestHelper.enterTabSwitcher(sActivityTestRule.getActivity());
        TabUiTestHelper.verifyAllTabsHaveThumbnail(
                sActivityTestRule.getActivity().getCurrentTabModel());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot.clickItemAtAdapterPosition(0);

        mRobot.resultRobot.verifyTabListEditorIsVisible();

        TabListRecyclerView tabListRecyclerView =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return mTabListEditorLayout.findViewById(R.id.tab_list_recycler_view);
                        });
        TabUiTestHelper.waitForThumbnailsToFetch(tabListRecyclerView);

        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "grid_view_v2_one_selected_tab_0.85");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_NON_LOW_END_DEVICE})
    public void testSelectionAction_Toggle() throws IOException {
        prepareBlankTabWithThumbnail(3, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        // Enter tab switcher to get all thumbnails.
        TabUiTestHelper.enterTabSwitcher(sActivityTestRule.getActivity());
        TabUiTestHelper.verifyAllTabsHaveThumbnail(
                sActivityTestRule.getActivity().getCurrentTabModel());
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorSelectionAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.ICON_AND_TEXT,
                                    IconPosition.END));

                    showSelectionEditor(tabs, actions);
                });

        final int selectionId = R.id.tab_list_editor_selection_menu_item;
        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Select all");

        mRobot.actionRobot.clickToolbarActionView(selectionId);
        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Deselect all")
                .verifyItemSelectedAtAdapterPosition(0)
                .verifyItemSelectedAtAdapterPosition(1)
                .verifyItemSelectedAtAdapterPosition(2)
                .verifyToolbarSelectionText("3 tabs");
        TabListRecyclerView tabListRecyclerView =
                ThreadUtils.runOnUiThreadBlocking(
                        () -> {
                            return mTabListEditorLayout.findViewById(R.id.tab_list_recycler_view);
                        });
        TabUiTestHelper.waitForThumbnailsToFetch(tabListRecyclerView);

        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "selection_action_all_tabs_selected");

        mRobot.actionRobot.clickToolbarActionView(selectionId);
        mRobot.resultRobot
                .verifyToolbarActionViewEnabled(selectionId)
                .verifyToolbarActionViewWithText(selectionId, "Select all")
                .verifyItemNotSelectedAtAdapterPosition(0)
                .verifyItemNotSelectedAtAdapterPosition(1)
                .verifyItemNotSelectedAtAdapterPosition(2)
                .verifyToolbarSelectionText("Select tabs");
        TabUiTestHelper.waitForThumbnailsToFetch(tabListRecyclerView);

        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "selection_action_all_tabs_deselected");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_LOW_END_DEVICE})
    public void testListViewAppearance() throws IOException {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.resultRobot.verifyTabListEditorIsVisible();

        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "list_view");
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_LOW_END_DEVICE})
    public void testListViewV2Shows() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        showSelectionEditor(tabs, null);

        mRobot.resultRobot.verifyTabListEditorIsVisible();
    }

    @Test
    @MediumTest
    @Feature({"RenderTest"})
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_LOW_END_DEVICE})
    public void testListViewAppearance_oneSelectedTab() throws IOException {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot.clickItemAtAdapterPosition(0);

        mRobot.resultRobot.verifyTabListEditorIsVisible();

        ChromeRenderTestRule.sanitize(mTabListEditorLayout);
        mRenderTestRule.render(mTabListEditorLayout, "list_view_one_selected_tab");
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE, RESTRICTION_TYPE_LOW_END_DEVICE})
    public void testListView_select() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });

        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        mRobot.resultRobot
                .verifyToolbarActionViewDisabled(R.id.tab_list_editor_group_menu_item)
                .verifyTabListEditorIsVisible();
    }

    @Test
    @MediumTest
    public void testTabListEditorLayoutCanBeGarbageCollected() {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabListEditorCoordinator.destroy();
                    mTabListEditorCoordinator = null;
                    mTabListEditorLayout = null;
                    mTabListEditorController = null;
                });

        InstrumentationRegistry.getInstrumentation().waitForIdleSync();

        // A longer timeout is needed. Achieve that by using the CriteriaHelper.pollUiThread.
        CriteriaHelper.pollUiThread(() -> GarbageCollectionTestUtils.canBeGarbageCollected(mRef));
    }

    @Test
    @MediumTest
    public void testSelectionTabAccessibilityChecked() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        // Test deselected tab
        View tabView =
                mTabListEditorCoordinator
                        .getTabListRecyclerViewForTesting()
                        .findViewHolderForAdapterPosition(0)
                        .itemView;
        assertFalse(tabView.createAccessibilityNodeInfo().isChecked());

        // Test selected tab
        mRobot.actionRobot.clickItemAtAdapterPosition(0);
        assertTrue(tabView.createAccessibilityNodeInfo().isChecked());
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    public void testToolbarMenuItem_SelectAllMenu() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorSelectionAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.MENU_ONLY,
                                    ButtonType.TEXT,
                                    IconPosition.START));
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.MENU_ONLY,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));

                    showSelectionEditor(tabs, actions);
                });
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Select all", /* enabled= */ true)
                .verifyToolbarMenuItemState("Close tabs", /* enabled= */ false);
        mRobot.actionRobot.clickToolbarMenuItem("Select all");
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Deselect all", /* enabled= */ true)
                .verifyToolbarMenuItemState("Close tabs", /* enabled= */ true);
        mRobot.actionRobot.clickToolbarMenuItem("Deselect all");
        mRobot.resultRobot.verifyToolbarMenuItemState("Select all", /* enabled= */ true);
        Espresso.pressBack();
    }

    @Test
    @MediumTest
    @Restriction({UiRestriction.RESTRICTION_TYPE_PHONE})
    public void testToolbarActionViewAndMenuItemContentDescription() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    List<TabListEditorAction> actions = new ArrayList<>();
                    actions.add(
                            TabListEditorCloseAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    ShowMode.IF_ROOM,
                                    ButtonType.TEXT,
                                    IconPosition.START,
                                    mActionConfirmationManager));
                    actions.add(
                            TabListEditorGroupAction.createAction(
                                    sActivityTestRule.getActivity(),
                                    mCreationDialogManager,
                                    ShowMode.MENU_ONLY,
                                    ButtonType.TEXT,
                                    IconPosition.START));

                    showSelectionEditor(tabs, actions);
                });
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        final int closeId = R.id.tab_list_editor_close_menu_item;
        View close = mTabListEditorLayout.getToolbar().findViewById(closeId);
        assertNull(close.getContentDescription());
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Group tabs", /* enabled= */ false)
                .verifyToolbarMenuItemWithContentDescription("Group tabs", null);
        Espresso.pressBack();

        for (int i = 0; i < tabs.size(); i++) {
            mRobot.actionRobot.clickItemAtAdapterPosition(i);
        }
        assertEquals("Close 2 selected tabs", close.getContentDescription());
        mRobot.actionRobot.clickToolbarMenuButton();
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Group tabs", /* enabled= */ true)
                .verifyToolbarMenuItemWithContentDescription("Group tabs", "Group 2 selected tabs");
        Espresso.pressBack();

        mRobot.actionRobot.clickItemAtAdapterPosition(0).clickToolbarMenuButton();
        assertEquals("Close 1 selected tab", close.getContentDescription());
        mRobot.resultRobot
                .verifyToolbarMenuItemState("Group tab", /* enabled= */ true)
                .verifyToolbarMenuItemWithContentDescription("Group tab", "Group 1 selected tab");
        Espresso.pressBack();
    }

    // This is a regression test for crbug.com/1132478.
    @Test
    @MediumTest
    public void testTabListEditorContentDescription() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();
        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        assertEquals("Multi-select mode", mTabListEditorLayout.getContentDescription());
    }

    @Test
    @MediumTest
    public void testToolbarNavigationButtonContentDescription() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();
        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        assertEquals(
                "Hide multi-select mode",
                mTabListEditorLayout.getToolbar().getNavigationContentDescription());
    }

    @Test
    @MediumTest
    public void testEditorHideCorrectly() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();
        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();

        ThreadUtils.runOnUiThreadBlocking(() -> mTabListEditorController.handleBackPressed());
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();
    }

    @Test
    @MediumTest
    public void testBackgroundViewAccessibilityImportance() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        Map<View, Integer> initialValues = getParentViewAccessibilityImportanceMap();

        showSelectionEditor(tabs, null);
        mRobot.resultRobot.verifyTabListEditorIsVisible();
        ViewGroup parentView = (ViewGroup) mTabListEditorLayout.getParent();
        verifyBackgroundViewAccessibilityImportance(parentView, true, initialValues);

        mRobot.actionRobot.clickToolbarNavigationButton();
        mRobot.resultRobot.verifyTabListEditorIsHidden();
        verifyBackgroundViewAccessibilityImportance(parentView, false, initialValues);
    }

    @Test
    @MediumTest
    public void testMoveToClosableState() {
        prepareBlankTab(2, false);
        List<Tab> tabs = getTabsInCurrentTabModel();

        showSelectionEditor(tabs, null);

        mRobot.resultRobot.verifyItemNotSelectedAtAdapterPosition(0);

        mRobot.actionRobot.clickActionButtonAdapterPosition(0, getActionButtonId());
        mRobot.resultRobot
                .verifyAdapterHasItemCount(2)
                .verifyItemSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionText("1 tab");

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabListEditorController.setTabActionState(TabActionState.CLOSABLE);
                });

        mRobot.resultRobot
                .verifyAdapterHasItemCount(2)
                .verifyItemNotSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionText("Select tabs");

        mRobot.actionRobot.clickActionButtonAdapterPosition(0, getActionButtonId());
        mRobot.resultRobot.verifyAdapterHasItemCount(1).verifyItemNotSelectedAtAdapterPosition(0);

        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabListEditorController.setTabActionState(TabActionState.SELECTABLE);
                });
        mRobot.actionRobot.clickActionButtonAdapterPosition(0, getActionButtonId());
        mRobot.resultRobot
                .verifyAdapterHasItemCount(1)
                .verifyItemSelectedAtAdapterPosition(0)
                .verifyToolbarSelectionText("1 tab");
    }

    private Map<View, Integer> getParentViewAccessibilityImportanceMap() {
        Map<View, Integer> map = new HashMap<>();

        for (int i = 0; i < mParentView.getChildCount(); i++) {
            View view = mParentView.getChildAt(i);
            map.put(view, view.getImportantForAccessibility());
        }

        map.put(mParentView, mParentView.getImportantForAccessibility());
        return map;
    }

    private void verifyBackgroundViewAccessibilityImportance(
            ViewGroup parentView,
            boolean isTabListEditorShowing,
            Map<View, Integer> initialValues) {
        assertEquals(
                isTabListEditorShowing
                        ? IMPORTANT_FOR_ACCESSIBILITY_NO
                        : initialValues.get(parentView).intValue(),
                parentView.getImportantForAccessibility());

        for (int i = 0; i < parentView.getChildCount(); i++) {
            View view = parentView.getChildAt(i);
            int expected =
                    isTabListEditorShowing
                            ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
                            : initialValues.get(view).intValue();
            if (view == mTabListEditorLayout) {
                expected = IMPORTANT_FOR_ACCESSIBILITY_YES;
            }

            assertEquals(expected, view.getImportantForAccessibility());
        }
    }

    /** Retrieves all tabs from the current tab model */
    private List<Tab> getTabsInCurrentTabModel() {
        List<Tab> tabs = new ArrayList<>();

        TabModel currentTabModel = mTabModelSelector.getCurrentModel();
        for (int i = 0; i < currentTabModel.getCount(); i++) {
            tabs.add(currentTabModel.getTabAt(i));
        }

        return tabs;
    }

    /**
     * Retrieves all non-grouped tabs and the last focused tab in each tab group from the current
     * tab model
     */
    private List<Tab> getTabsInCurrentTabModelFilter() {
        List<Tab> tabs = new ArrayList<>();

        TabGroupModelFilter filter =
                (TabGroupModelFilter)
                        mTabModelSelector.getTabModelFilterProvider().getCurrentTabModelFilter();
        for (int i = 0; i < filter.getCount(); i++) {
            tabs.add(filter.getTabAt(i));
        }

        return tabs;
    }

    private void showSelectionEditor(List<Tab> tabs, @Nullable List<TabListEditorAction> actions) {
        ThreadUtils.runOnUiThreadBlocking(
                () -> {
                    mTabListEditorController.show(tabs, /* recyclerViewPosition= */ null);
                    if (actions != null) {
                        mTabListEditorController.configureToolbarWithMenuItems(actions);
                    }
                });
    }

    private @IdRes int getActionButtonId() {
        if (getMode() == TabListCoordinator.TabListMode.GRID) {
            return R.id.action_button;
        } else {
            return R.id.end_button;
        }
    }
}