chromium/chrome/android/junit/src/org/chromium/chrome/browser/compositor/overlays/strip/StripLayoutHelperManagerTest.java

// Copyright 2022 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.compositor.overlays.strip;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.multiwindow.MultiWindowTestUtils.enableMultiInstance;

import android.app.Activity;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.os.Build.VERSION_CODES;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewStub;

import androidx.annotation.ColorInt;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.core.graphics.ColorUtils;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.annotation.Config;

import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.JniMocker;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.browser_controls.BrowserControlsStateProvider;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
import org.chromium.chrome.browser.compositor.layouts.LayoutManagerHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutRenderHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.compositor.layouts.components.TintedCompositorButton;
import org.chromium.chrome.browser.compositor.layouts.eventfilter.AreaMotionEventFilter;
import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager.StripVisibilityState;
import org.chromium.chrome.browser.compositor.overlays.strip.StripLayoutHelperManager.TabModelStartupInfo;
import org.chromium.chrome.browser.compositor.scene_layer.TabStripSceneLayer;
import org.chromium.chrome.browser.compositor.scene_layer.TabStripSceneLayerJni;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.layouts.LayoutType;
import org.chromium.chrome.browser.layouts.components.VirtualView;
import org.chromium.chrome.browser.lifecycle.ActivityLifecycleDispatcher;
import org.chromium.chrome.browser.multiwindow.MultiInstanceManager;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab_ui.TabContentManager;
import org.chromium.chrome.browser.tabmodel.TabCreatorManager;
import org.chromium.chrome.browser.tabmodel.TabModel;
import org.chromium.chrome.browser.tabmodel.TabModelFilterProvider;
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;
import org.chromium.chrome.browser.tasks.tab_management.TabUiThemeUtil;
import org.chromium.chrome.browser.toolbar.ToolbarFeatures;
import org.chromium.chrome.browser.toolbar.ToolbarManager;
import org.chromium.chrome.browser.ui.desktop_windowing.AppHeaderState;
import org.chromium.chrome.browser.ui.desktop_windowing.DesktopWindowStateProvider;
import org.chromium.chrome.browser.ui.system.StatusBarColorController;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.browser_ui.styles.SemanticColorUtils;
import org.chromium.components.browser_ui.widget.scrim.ScrimProperties;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.base.WindowAndroid;
import org.chromium.ui.dragdrop.DragAndDropDelegate;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

/** Tests for {@link StripLayoutHelperManager}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, qualifiers = "sw600dp")
@DisableFeatures(ChromeFeatureList.TAB_STRIP_INCOGNITO_MIGRATION)
public class StripLayoutHelperManagerTest {
    @Rule public JniMocker mJniMocker = new JniMocker();
    @Mock private TabStripSceneLayer.Natives mTabStripSceneMock;
    @Mock private TabStripSceneLayer mTabStripTreeProvider;
    @Mock private LayoutManagerHost mManagerHost;
    @Mock private LayoutUpdateHost mUpdateHost;
    @Mock private LayoutRenderHost mRenderHost;
    @Mock private ObservableSupplierImpl<LayerTitleCache> mLayerTitleCacheSupplier;
    @Mock private ActivityLifecycleDispatcher mLifecycleDispatcher;
    @Mock private MultiInstanceManager mMultiInstanceManager;
    @Mock private View mToolbarContainerView;
    @Mock private DragAndDropDelegate mDragDropDelegate;
    @Mock private TabModelSelector mTabModelSelector;
    @Mock private ObservableSupplierImpl<TabModel> mTabModelSupplier;
    @Mock private TabCreatorManager mTabCreatorManager;
    @Mock private TabGroupModelFilter mTabGroupModelFilter;
    @Mock private TabModelFilterProvider mTabModelFilterProvider;
    @Mock private TabModel mStandardTabModel;
    @Mock private Tab mSelectedTab;
    @Mock private StripLayoutTab mHoveredStripTab;
    @Mock private ViewStub mTabHoverCardViewStub;
    @Mock private ObservableSupplierImpl<TabContentManager> mTabContentManagerSupplier;
    @Mock private BrowserControlsStateProvider mBrowserControlStateProvider;
    @Mock private WindowAndroid mWindowAndroid;
    @Mock private ToolbarManager mToolbarManager;
    @Mock private StatusBarColorController mStatusBarColorController;
    @Mock private DesktopWindowStateProvider mDesktopWindowStateProvider;
    @Mock private ActionConfirmationManager mActionConfirmationManager;
    @Captor private ArgumentCaptor<List<Rect>> mSystemExclusionRectCaptor;

    private StripLayoutHelperManager mStripLayoutHelperManager;
    private Activity mActivity;
    private ObservableSupplierImpl<TabModelStartupInfo> mTabModelStartupInfoSupplier;
    private ObservableSupplierImpl<Integer> mTabStripHeightSupplier;
    private int mToolbarPrimaryColor;
    private static final float SCREEN_WIDTH = 800.f;
    private static final float SCREEN_HEIGHT = 1600.f;
    private static final float VISIBLE_VIEWPORT_Y = 200.f;
    private static final int ORIENTATION = 2;
    private static final float BUTTON_END_PADDING = 8.f;
    private static final int TAB_STRIP_HEIGHT_PX = 40;
    private static final int FADE_TRANSITION_DURATION_MS = 200;

    @Before
    public void beforeTest() {
        MockitoAnnotations.initMocks(this);
        mJniMocker.mock(TabStripSceneLayerJni.TEST_HOOKS, mTabStripSceneMock);
        mActivity = Robolectric.buildActivity(Activity.class).setup().get();
        mActivity.setTheme(R.style.Theme_BrowserUI_DayNight);
        when(mToolbarContainerView.getContext()).thenReturn(mActivity);
        when(mToolbarManager.getStatusBarColorController()).thenReturn(mStatusBarColorController);

        TabStripSceneLayer.setTestFlag(true);

        when(mDesktopWindowStateProvider.isInUnfocusedDesktopWindow()).thenReturn(false);
        when(mWindowAndroid.getActivity()).thenReturn(new WeakReference<>(mActivity));
        initializeTest();
    }

    @After
    public void tearDown() {
        TabStripSceneLayer.setTestFlag(false);
    }

    private void initializeTest() {
        when(mTabModelFilterProvider.getTabModelFilter(anyBoolean()))
                .thenReturn(mTabGroupModelFilter);
        when(mTabModelSelector.getTabModelFilterProvider()).thenReturn(mTabModelFilterProvider);
        when(mTabModelSelector.getCurrentModel()).thenReturn(mStandardTabModel);
        when(mTabModelSelector.getCurrentTabModelSupplier()).thenReturn(mTabModelSupplier);

        mTabModelStartupInfoSupplier = new ObservableSupplierImpl<>();

        mTabStripHeightSupplier = new ObservableSupplierImpl<>();
        mTabStripHeightSupplier.set(TAB_STRIP_HEIGHT_PX);
        mToolbarPrimaryColor = SemanticColorUtils.getToolbarBackgroundPrimary(mActivity);
        when(mToolbarManager.getTabStripHeightSupplier()).thenReturn(mTabStripHeightSupplier);
        when(mToolbarManager.getPrimaryColor()).thenReturn(mToolbarPrimaryColor);

        mStripLayoutHelperManager =
                new StripLayoutHelperManager(
                        mActivity,
                        mManagerHost,
                        mUpdateHost,
                        mRenderHost,
                        mLayerTitleCacheSupplier,
                        mTabModelStartupInfoSupplier,
                        mLifecycleDispatcher,
                        mMultiInstanceManager,
                        mDragDropDelegate,
                        mToolbarContainerView,
                        mTabHoverCardViewStub,
                        mTabContentManagerSupplier,
                        mBrowserControlStateProvider,
                        mWindowAndroid,
                        mToolbarManager,
                        mDesktopWindowStateProvider,
                        mActionConfirmationManager);
        mStripLayoutHelperManager.setTabModelSelector(mTabModelSelector, mTabCreatorManager);
        mStripLayoutHelperManager.disableAnimationsForTesting();
    }

    @Test
    public void testGetBackgroundColor_ActivityFocusChange_TsloDisabled() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(false);
        assertEquals(
                "Initial strip background color is incorrect.",
                ChromeColors.getSurfaceColor(mActivity, R.dimen.default_elevation_3),
                mStripLayoutHelperManager.getBackgroundColor());
        // Assume the current activity lost focus.
        mStripLayoutHelperManager.onTopResumedActivityChanged(false);
        assertEquals(
                "Strip background color should not be updated when activity focus state changes.",
                ChromeColors.getSurfaceColor(mActivity, R.dimen.default_elevation_3),
                mStripLayoutHelperManager.getBackgroundColor());
    }

    @Test
    public void testGetBackgroundColor_ActivityFocusChange_NotInDesktopWindow() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        when(mDesktopWindowStateProvider.getAppHeaderState()).thenReturn(new AppHeaderState());
        assertEquals(
                "Initial strip background color is incorrect.",
                ChromeColors.getSurfaceColor(mActivity, R.dimen.default_elevation_3),
                mStripLayoutHelperManager.getBackgroundColor());
        // Assume the current activity lost focus.
        mStripLayoutHelperManager.onTopResumedActivityChanged(false);
        assertEquals(
                "Strip background color should not be updated when activity focus state changes"
                        + " while not in desktop window.",
                ChromeColors.getSurfaceColor(mActivity, R.dimen.default_elevation_3),
                mStripLayoutHelperManager.getBackgroundColor());
    }

    @Test
    public void testGetBackgroundColor_ActivityFocusChange_LightTheme() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();
        doTestBackgroundColorOnActivityFocusChange(
                /* isNightMode= */ false, /* isIncognito= */ false);
    }

    @Test
    @Config(qualifiers = "night")
    public void testGetBackgroundColor_ActivityFocusChange_DarkTheme() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();
        doTestBackgroundColorOnActivityFocusChange(
                /* isNightMode= */ true, /* isIncognito= */ false);
    }

    @Test
    public void testGetBackgroundColor_ActivityFocusChange_Incognito() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();
        mStripLayoutHelperManager.setIsIncognitoForTesting(true);
        doTestBackgroundColorOnActivityFocusChange(
                /* isNightMode= */ false, /* isIncognito= */ true);
    }

    private void doTestBackgroundColorOnActivityFocusChange(
            boolean isNightMode, boolean isIncognito) {
        var appHeaderState = Mockito.mock(AppHeaderState.class);
        doReturn(true).when(appHeaderState).isInDesktopWindow();
        when(mDesktopWindowStateProvider.getAppHeaderState()).thenReturn(appHeaderState);
        @ColorInt
        int focusedColor =
                ChromeColors.getSurfaceColor(
                        mActivity,
                        isNightMode ? R.dimen.default_elevation_2 : R.dimen.default_elevation_3);
        @ColorInt
        int unfocusedColor =
                ChromeColors.getSurfaceColor(
                        mActivity,
                        isNightMode ? R.dimen.default_elevation_1 : R.dimen.default_elevation_2);

        if (isIncognito) {
            focusedColor = mActivity.getColor(R.color.default_bg_color_dark_elev_2_baseline);
            unfocusedColor = mActivity.getColor(R.color.default_bg_color_dark_elev_1_baseline);
        }

        // Initially use the default tab strip background.
        assertEquals(
                "Initial strip background color is incorrect.",
                focusedColor,
                mStripLayoutHelperManager.getBackgroundColor());
        // Assume the current activity lost focus.
        mStripLayoutHelperManager.onTopResumedActivityChanged(false);
        assertEquals(
                "Strip background color should be updated when activity focus state changes to"
                        + " false.",
                unfocusedColor,
                mStripLayoutHelperManager.getBackgroundColor());
        // Assume the current activity gained focus.
        mStripLayoutHelperManager.onTopResumedActivityChanged(true);
        assertEquals(
                "Strip background color should be updated when activity focus state changes to"
                        + " true.",
                focusedColor,
                mStripLayoutHelperManager.getBackgroundColor());
    }

    @Test
    public void testGetBackgroundColor_ActivityStartsInUnfocusedDesktopWindow() {
        // Assume that the app starts in an unfocused desktop window.
        var appHeaderState =
                new AppHeaderState(new Rect(), new Rect(), /* isInDesktopWindow= */ true);
        when(mDesktopWindowStateProvider.getAppHeaderState()).thenReturn(appHeaderState);
        when(mDesktopWindowStateProvider.isInUnfocusedDesktopWindow()).thenReturn(true);
        initializeTest();

        @ColorInt
        int unfocusedLightThemeColor =
                ChromeColors.getSurfaceColor(mActivity, R.dimen.default_elevation_2);
        assertEquals(
                "Strip background color is incorrect.",
                unfocusedLightThemeColor,
                mStripLayoutHelperManager.getBackgroundColor());
    }

    @Test
    public void testUpdateForeGroundColor() {
        initializeTest();

        mStripLayoutHelperManager.onAppHeaderStateChanged(new AppHeaderState());

        int normalColor = TabUiThemeUtil.getTabStripBackgroundColor(mActivity, false);
        verify(mDesktopWindowStateProvider).updateForegroundColor(normalColor);

        Mockito.reset(mDesktopWindowStateProvider);
        mStripLayoutHelperManager.setIsIncognitoForTesting(true);
        mStripLayoutHelperManager.onAppHeaderStateChanged(new AppHeaderState());

        int incognitoColor = TabUiThemeUtil.getTabStripBackgroundColor(mActivity, true);
        verify(mDesktopWindowStateProvider).updateForegroundColor(incognitoColor);
    }

    @Test
    @DisableFeatures(ChromeFeatureList.TAB_STRIP_LAYOUT_OPTIMIZATION)
    public void testModelSelectorButtonXPosition() {
        // Set model selector button position.
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);

        // Verify model selector button x-position.
        // stripWidth(800) - buttonEndPadding(8) - MsbWidth(32) = 760
        assertEquals(
                "Model selector button x-position is not as expected",
                760.f,
                mStripLayoutHelperManager.getModelSelectorButton().getDrawX(),
                0.0);
    }

    @Test
    @DisableFeatures(ChromeFeatureList.TAB_STRIP_LAYOUT_OPTIMIZATION)
    public void testModelSelectorButtonXPosition_RTL() {
        // Set model selector button position.
        LocalizationUtils.setRtlForTesting(true);
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);

        // Verify model selector button position.
        assertEquals(
                "Model selector button x-position is not as expected",
                BUTTON_END_PADDING,
                mStripLayoutHelperManager.getModelSelectorButton().getDrawX(),
                0.0);
    }

    @Test
    public void testModelSelectorButtonYPosition() {
        // Set model selector button position.
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);

        // Verify model selector button y-position.
        assertEquals(
                "Model selector button y-position is not as expected",
                3.f,
                mStripLayoutHelperManager.getModelSelectorButton().getDrawY(),
                0.0);
    }

    @Test
    @Feature("Advanced Peripherals Support")
    public void testModelSelectorButtonHoverHighlightProperties() {
        // Set model selector button position.
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);

        // Verify model selector button hover highlight resource id.
        assertEquals(
                "Model selector button hover highlight is not as expected",
                R.drawable.bg_circle_tab_strip_button,
                ((TintedCompositorButton) mStripLayoutHelperManager.getModelSelectorButton())
                        .getBackgroundResourceId());

        // Verify model selector button hover highlight default tint.
        TintedCompositorButton msb =
                ((TintedCompositorButton) spy(mStripLayoutHelperManager.getModelSelectorButton()));
        when(msb.isHovered()).thenReturn(true);
        when(msb.isPressedFromMouse()).thenReturn(false);

        @ColorInt
        int hoverBackgroundDefaultColor =
                ColorUtils.setAlphaComponent(
                        SemanticColorUtils.getDefaultTextColor(mActivity), (int) (0.08 * 255));
        assertEquals(
                "Model selector button hover highlight default tint is not as expected",
                hoverBackgroundDefaultColor,
                msb.getBackgroundTint());

        // Verify model selector button hover highlight pressed tint.
        when(msb.isPressed()).thenReturn(true);
        when(msb.isHovered()).thenReturn(false);
        when(msb.isPressedFromMouse()).thenReturn(true);
        @ColorInt
        int hoverBackgroundPressedColor =
                ColorUtils.setAlphaComponent(
                        SemanticColorUtils.getDefaultTextColor(mActivity), (int) (0.12 * 255));
        assertEquals(
                "Model selector button hover highlight pressed tint is not as expected",
                hoverBackgroundPressedColor,
                msb.getBackgroundTint());
        when(msb.isPressed()).thenReturn(false);

        // Verify model selector button incognito hover highlight default tint.
        when(msb.isHovered()).thenReturn(true);
        when(msb.isIncognito()).thenReturn(true);
        @ColorInt
        int hoverBackgroundDefaultIncognitoColor =
                ColorUtils.setAlphaComponent(
                        mActivity.getColor(R.color.tab_strip_button_hover_bg_color),
                        (int) (0.08 * 255));
        assertEquals(
                "Model selector button hover highlight pressed tint is not as expected",
                hoverBackgroundDefaultIncognitoColor,
                msb.getBackgroundTint());

        // Verify model selector button incognito hover highlight pressed tint.
        when(msb.isPressed()).thenReturn(true);
        when(msb.isHovered()).thenReturn(false);
        when(msb.isPressedFromMouse()).thenReturn(true);
        @ColorInt
        int hoverBackgroundPressedIncognitoColor =
                ColorUtils.setAlphaComponent(
                        mActivity.getColor(R.color.tab_strip_button_hover_bg_color),
                        (int) (0.12 * 255));
        assertEquals(
                "Model selector button hover highlight pressed tint is not as expected",
                hoverBackgroundPressedIncognitoColor,
                msb.getBackgroundTint());
    }

    @Test
    @Feature("Advanced Peripherals Support")
    public void testModelSelectorButtonHoverEnter() {
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);

        int x = (int) mStripLayoutHelperManager.getModelSelectorButton().getDrawX();
        mStripLayoutHelperManager
                .getActiveStripLayoutHelper()
                .onHoverEnter(
                        x + 1, 0); // mouse position within MSB range(32dp width + 12dp click slop).
        assertTrue(
                "Model selector button should be hovered",
                mStripLayoutHelperManager.getModelSelectorButton().isHovered());

        // Verify model selector button is NOT hovered when mouse is not on the button.
        mStripLayoutHelperManager
                .getActiveStripLayoutHelper()
                .onHoverEnter(
                        x + 45,
                        0); // mouse position out of MSB range(32dp width + 12dp click slop).
        assertFalse(
                "Model selector button should NOT be hovered",
                mStripLayoutHelperManager.getModelSelectorButton().isHovered());
    }

    @Test
    @Feature("Advanced Peripherals Support")
    public void testModelSelectorButtonHoverOnDown() {
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);

        // Verify model selector button is in pressed state, not hover state, when click is from
        // mouse.
        mStripLayoutHelperManager.simulateOnDownForTesting(
                mStripLayoutHelperManager.getModelSelectorButton().getDrawX() + 1, 0, true, 1);
        assertFalse(
                "Model selector button should not be hovered",
                mStripLayoutHelperManager.getModelSelectorButton().isHovered());
        assertTrue(
                "Model selector button should be pressed from mouse",
                mStripLayoutHelperManager.getModelSelectorButton().isPressedFromMouse());
    }

    @Test
    public void testFadeDrawable_Right_ModelSelectorButtonVisible() {
        // setup
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);

        // Verify fade drawable.
        assertEquals(
                "Fade drawable resource is not as expected",
                R.drawable.tab_strip_fade_long,
                mStripLayoutHelperManager.getRightFadeDrawable());
    }

    @Test
    public void testFadeDrawable_Right() {
        // Verify fade drawable.
        assertEquals(
                "Fade drawable resource is not as expected",
                R.drawable.tab_strip_fade_medium,
                mStripLayoutHelperManager.getRightFadeDrawable());
    }

    @Test
    public void testFadeDrawable_Left() {
        // setup
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);

        // Verify fade drawable.
        assertEquals(
                "Fade drawable resource is not as expected",
                R.drawable.tab_strip_fade_short,
                mStripLayoutHelperManager.getLeftFadeDrawable());
    }

    @Test
    public void testFadeDrawable_Left_RTL_ModelSelectorButtonVisible() {
        // setup
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);
        LocalizationUtils.setRtlForTesting(true);

        // Verify fade drawable.
        assertEquals(
                "Fade drawable resource is not as expected",
                R.drawable.tab_strip_fade_long,
                mStripLayoutHelperManager.getLeftFadeDrawable());
    }

    @Test
    public void testFadeDrawable_Left_RTL() {
        // setup
        LocalizationUtils.setRtlForTesting(true);

        // Verify fade drawable.
        assertEquals(
                "Fade drawable resource is not as expected",
                R.drawable.tab_strip_fade_medium,
                mStripLayoutHelperManager.getLeftFadeDrawable());
    }

    @Test
    public void testFadeDrawable_Right_RTL() {
        // setup
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);
        LocalizationUtils.setRtlForTesting(true);

        // Verify fade drawable.
        assertEquals(
                "Fade drawable resource is not as expected",
                R.drawable.tab_strip_fade_short,
                mStripLayoutHelperManager.getRightFadeDrawable());
    }

    @Test
    public void testButtonIconColor_DisableButtonStyle() {
        // Verify button icon color after disabling button style.
        assertEquals(
                "Unexpected incognito button color.",
                AppCompatResources.getColorStateList(
                                mActivity, R.color.default_icon_color_tint_list)
                        .getDefaultColor(),
                ((TintedCompositorButton) mStripLayoutHelperManager.getModelSelectorButton())
                        .getTint());
    }

    @Test
    @Feature("TabStripPerformance")
    @EnableFeatures(ChromeFeatureList.TAB_STRIP_STARTUP_REFACTORING)
    public void testSetTabModelStartupInfo() {
        // Setup
        int expectedStandardCount = 5;
        int expectedIncognitoCount = 0;
        int expectedStandardActiveTabIndex = 2;
        int expectedIncognitoActiveTabIndex = Tab.INVALID_TAB_ID;
        boolean expectedStandardCreatedTabOnStartup = false;
        boolean expectedIncognitoCreatedTabOnStartup = false;
        TabModelStartupInfo startupInfo =
                new TabModelStartupInfo(
                        expectedStandardCount,
                        expectedIncognitoCount,
                        expectedStandardActiveTabIndex,
                        expectedIncognitoActiveTabIndex,
                        expectedStandardCreatedTabOnStartup,
                        expectedIncognitoCreatedTabOnStartup);
        mTabModelStartupInfoSupplier.set(startupInfo);

        // Verify
        StripLayoutHelper standardHelper = mStripLayoutHelperManager.getStripLayoutHelper(false);
        assertEquals(
                "Unexpected standard tab count.",
                expectedStandardCount,
                standardHelper.getTabCountOnStartupForTesting());
        assertEquals(
                "Unexpected standard active tab index.",
                expectedStandardActiveTabIndex,
                standardHelper.getActiveTabIndexOnStartupForTesting());
        assertEquals(
                "Unexpected standard tab created on startup value",
                expectedStandardCreatedTabOnStartup,
                standardHelper.getCreatedTabOnStartupForTesting());

        StripLayoutHelper incognitoHelper = mStripLayoutHelperManager.getStripLayoutHelper(true);
        assertEquals(
                "Unexpected incognito tab count.",
                expectedIncognitoCount,
                incognitoHelper.getTabCountOnStartupForTesting());
        assertEquals(
                "Unexpected incognito active tab index.",
                expectedIncognitoActiveTabIndex,
                incognitoHelper.getActiveTabIndexOnStartupForTesting());
        assertEquals(
                "Unexpected incognito tab created on startup value",
                expectedIncognitoCreatedTabOnStartup,
                standardHelper.getCreatedTabOnStartupForTesting());
    }

    @Test
    public void testGetUpdatedSceneOverlayTree() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();
        // Setup and stub required mocks.
        int hoveredTabId = 1;
        int selectedTabId = 2;
        mStripLayoutHelperManager.setTabStripTreeProviderForTesting(mTabStripTreeProvider);

        when(mStandardTabModel.index()).thenReturn(selectedTabId);
        when(mStandardTabModel.getTabAt(selectedTabId)).thenReturn(mSelectedTab);
        when(mSelectedTab.getId()).thenReturn(selectedTabId);

        when(mHoveredStripTab.getTabId()).thenReturn(hoveredTabId);
        var activeLayoutHelper = mStripLayoutHelperManager.getActiveStripLayoutHelper();
        activeLayoutHelper.setLastHoveredTabForTesting(mHoveredStripTab);

        // Update the paddings.
        int leftPadding = 10;
        int rightPadding = 20;
        int topPaddingPx = 5;
        var appHeaderState =
                new AppHeaderState(
                        new Rect(0, 0, (int) SCREEN_WIDTH, (int) SCREEN_HEIGHT),
                        new Rect(
                                leftPadding,
                                0,
                                (int) (SCREEN_WIDTH - rightPadding),
                                TAB_STRIP_HEIGHT_PX + topPaddingPx),
                        true);
        mStripLayoutHelperManager.onAppHeaderStateChanged(appHeaderState);
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX + topPaddingPx);

        // Invoke the method.
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), 0f);

        // Verify the call to #pushAndUpdateStrip.
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        eq(mStripLayoutHelperManager),
                        any(),
                        any(),
                        any(),
                        any(),
                        eq(0f),
                        eq(selectedTabId),
                        eq(hoveredTabId),
                        anyInt(),
                        anyFloat(),
                        eq((float) leftPadding),
                        eq((float) rightPadding),
                        eq((float) topPaddingPx));
    }

    @Test
    @Config(sdk = VERSION_CODES.R)
    public void testDragDropInstances_Success() {
        enableMultiInstance();
        initializeTest();
        assertNotNull(
                "Tab drag source should be set.",
                mStripLayoutHelperManager.getTabDragSourceForTesting());
    }

    @Test
    @Config(sdk = VERSION_CODES.Q)
    public void testDragDropInstances_MultiInstanceNotEnabled_ReturnsNull() {
        initializeTest();
        assertNull(
                "Tab drag source should not be set.",
                mStripLayoutHelperManager.getTabDragSourceForTesting());
    }

    @Test
    @Config(sdk = VERSION_CODES.S)
    public void testGetDragListener() {
        enableMultiInstance();
        initializeTest();
        assertNotNull("DragListener should be set.", mStripLayoutHelperManager.getDragListener());
    }

    @Test
    @DisableFeatures(ChromeFeatureList.TAB_STRIP_LAYOUT_OPTIMIZATION)
    public void testTabStripHeightTransition_Hide() {
        mStripLayoutHelperManager.setTabStripTreeProviderForTesting(mTabStripTreeProvider);

        // Call without tab strip transition.
        float yOffset = 10;
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), yOffset);
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        any(),
                        any(),
                        any(),
                        any(),
                        any(),
                        /* yOffset= */ eq(yOffset),
                        anyInt(),
                        anyInt(),
                        eq(mToolbarPrimaryColor),
                        /* scrimOpacity= */ eq(0f),
                        anyFloat(),
                        anyFloat(),
                        anyFloat());

        // With tab strip transition, the yOffset will be forced to be 0.
        mTabStripHeightSupplier.set(0);
        mStripLayoutHelperManager.onHeightChanged(0);
        float progress = 0.75f; // 1 - yOffset / TAB_STRIP_HEIGHT = 1 - 10 / 40 = 0.75f
        float expectedOpacity =
                StripLayoutHelperManager.TAB_STRIP_TRANSITION_INTERPOLATOR.getInterpolation(
                        progress);
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), yOffset);
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        any(),
                        any(),
                        any(),
                        any(),
                        any(),
                        /* yOffset= */ eq(0f),
                        anyInt(),
                        anyInt(),
                        eq(mToolbarPrimaryColor),
                        /* scrimOpacity= */ eq(expectedOpacity),
                        anyFloat(),
                        anyFloat(),
                        anyFloat());

        // With tab strip transition finished, the yOffset will be forced to be the negative of the
        // tab strip height.
        mStripLayoutHelperManager.onHeightTransitionFinished();
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), yOffset);
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        any(),
                        any(),
                        any(),
                        any(),
                        any(),
                        /* yOffset= */ eq(yOffset - TAB_STRIP_HEIGHT_PX),
                        anyInt(),
                        anyInt(),
                        eq(mToolbarPrimaryColor),
                        /* scrimOpacity= */ eq(0f),
                        anyFloat(),
                        anyFloat(),
                        anyFloat());

        // Verify StatusBarColorController method invocations.
        InOrder inOrder = Mockito.inOrder(mStatusBarColorController);
        // Invocation during the transition.
        inOrder.verify(mStatusBarColorController)
                .setTabStripColorOverlay(mToolbarPrimaryColor, expectedOpacity);
        // Invocation after the transition finished.
        inOrder.verify(mStatusBarColorController).setTabStripHiddenOnTablet(true);
        inOrder.verify(mStatusBarColorController)
                .setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
    }

    @Test
    @DisableFeatures(ChromeFeatureList.TAB_STRIP_LAYOUT_OPTIMIZATION)
    public void testTabStripHeightTransition_Show() {
        doTestTabStripHeightTransition_Show(mToolbarPrimaryColor);
    }

    @Test
    public void testGetVirtualViews() {
        List<VirtualView> views = new ArrayList<>();
        mStripLayoutHelperManager.getVirtualViews(views);
        assertFalse("Views are not empty during regular mode.", views.isEmpty());
    }

    @Test
    public void testGetVirtualViews_TabSwitcher() {
        List<VirtualView> views = new ArrayList<>();
        mStripLayoutHelperManager
                .getTabSwitcherObserver()
                .onFinishedShowing(LayoutType.TAB_SWITCHER);
        mStripLayoutHelperManager.getVirtualViews(views);
        assertTrue("Views are empty when tab switcher is showing.", views.isEmpty());

        mStripLayoutHelperManager.getTabSwitcherObserver().onStartedHiding(LayoutType.TAB_SWITCHER);
        mStripLayoutHelperManager.getVirtualViews(views);
        assertFalse("Views are not empty after tab switcher is hiding.", views.isEmpty());
    }

    @Test
    public void testGetVirtualViews_BrowserControlsOffset() {
        List<VirtualView> views = new ArrayList<>();
        doReturn(-1).when(mBrowserControlStateProvider).getTopControlOffset();
        mStripLayoutHelperManager.getVirtualViews(views);
        assertTrue("Views empty when browser controls partially visible.", views.isEmpty());
    }

    @Test
    public void testGetVirtualViews_TabStripHeightTransition() {
        List<VirtualView> views = new ArrayList<>();
        mStripLayoutHelperManager.setIsTabStripHidden(true);
        mStripLayoutHelperManager.getVirtualViews(views);
        assertTrue("Views are empty when tab strip hidden.", views.isEmpty());
        verify(mStatusBarColorController).setTabStripHiddenOnTablet(true);

        mStripLayoutHelperManager.setIsTabStripHidden(false);
        mStripLayoutHelperManager.onHeightChanged(40);
        mStripLayoutHelperManager.getVirtualViews(views);
        assertTrue("Views are empty during tab strip transition.", views.isEmpty());
        // Invoked once by #setIsTabStripHidden(), once by #onHeightChanged().
        verify(mStatusBarColorController, times(2)).setTabStripHiddenOnTablet(false);

        mStripLayoutHelperManager.onHeightTransitionFinished();
        mStripLayoutHelperManager.getVirtualViews(views);
        assertFalse("Views are not empty after tab strip transition.", views.isEmpty());
    }

    @Test
    public void testCalculateScrimOpacityDuringTransition_Show() {
        // Test hide->show transition with simulated values.
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX);
        float actual = mStripLayoutHelperManager.calculateScrimOpacityDuringHeightTransition(20f);
        float expected =
                StripLayoutHelperManager.TAB_STRIP_TRANSITION_INTERPOLATOR.getInterpolation(0.5f);
        assertEquals(expected, actual, 0f);
        actual = mStripLayoutHelperManager.calculateScrimOpacityDuringHeightTransition(30f);
        expected =
                StripLayoutHelperManager.TAB_STRIP_TRANSITION_INTERPOLATOR.getInterpolation(0.25f);
        assertEquals(expected, actual, 0f);
        // If an unexpected source happened to update the compositor frame during strip transition
        // when the yOffset=0, ignore this update.
        actual = mStripLayoutHelperManager.calculateScrimOpacityDuringHeightTransition(0f);
        assertEquals(expected, actual, 0f);
        mStripLayoutHelperManager.onHeightTransitionFinished();
    }

    @Test
    public void testCalculateScrimOpacityDuringTransition_Hide() {
        // Test show->hide transition with simulated values.
        mStripLayoutHelperManager.onHeightChanged(0);
        float actual = mStripLayoutHelperManager.calculateScrimOpacityDuringHeightTransition(30f);
        float expected =
                StripLayoutHelperManager.TAB_STRIP_TRANSITION_INTERPOLATOR.getInterpolation(0.25f);
        assertEquals(expected, actual, 0f);
        actual = mStripLayoutHelperManager.calculateScrimOpacityDuringHeightTransition(20f);
        expected =
                StripLayoutHelperManager.TAB_STRIP_TRANSITION_INTERPOLATOR.getInterpolation(0.5f);
        assertEquals(expected, actual, 0f);
        // If an unexpected source happened to update the compositor frame during strip transition
        // when the yOffset=-10, ignore this update.
        actual = mStripLayoutHelperManager.calculateScrimOpacityDuringHeightTransition(30f);
        assertEquals(expected, actual, 0f);
        mStripLayoutHelperManager.onHeightTransitionFinished();
    }

    private void doTestTabStripHeightTransition_Show(int scrimColor) {
        // Assume tab strip is hidden from the beginning.
        mTabStripHeightSupplier.set(0);
        mStripLayoutHelperManager.onHeightChanged(0);
        mStripLayoutHelperManager.onHeightTransitionFinished();
        mStripLayoutHelperManager.setTabStripTreeProviderForTesting(mTabStripTreeProvider);

        // The yOffset will be forced to be reduced by the tab strip height to be kept invisible.
        float yOffset = -10;
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), yOffset);
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        any(),
                        any(),
                        any(),
                        any(),
                        any(),
                        /* yOffset= */ eq(yOffset - TAB_STRIP_HEIGHT_PX),
                        anyInt(),
                        anyInt(),
                        eq(scrimColor),
                        /* scrimOpacity= */ eq(0f),
                        anyFloat(),
                        anyFloat(),
                        anyFloat());

        // With tab strip transition, the yOffset will be forced to be 0.
        mTabStripHeightSupplier.set(TAB_STRIP_HEIGHT_PX);
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX);
        float progress =
                0.25f; // 1 - (TAB_STRIP_HEIGHT+yOffset) / TAB_STRIP_HEIGHT = 1 - 30 / 40 = 0.25f
        float expectedOpacity =
                StripLayoutHelperManager.TAB_STRIP_TRANSITION_INTERPOLATOR.getInterpolation(
                        progress);
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), yOffset);
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        any(),
                        any(),
                        any(),
                        any(),
                        any(),
                        /* yOffset= */ eq(0f),
                        anyInt(),
                        anyInt(),
                        eq(scrimColor),
                        /* scrimOpacity= */ eq(expectedOpacity),
                        anyFloat(),
                        anyFloat(),
                        anyFloat());

        // When transition finished while tabs strip showing, yOffset will be forwarded to cc
        // correctly.
        mStripLayoutHelperManager.onHeightTransitionFinished();
        mStripLayoutHelperManager.getUpdatedSceneOverlayTree(
                new RectF(), new RectF(), mRenderHost.getResourceManager(), yOffset);
        verify(mTabStripTreeProvider)
                .pushAndUpdateStrip(
                        any(),
                        any(),
                        any(),
                        any(),
                        any(),
                        /* yOffset= */ eq(yOffset),
                        anyInt(),
                        anyInt(),
                        eq(scrimColor),
                        /* scrimOpacity= */ eq(0f),
                        anyFloat(),
                        anyFloat(),
                        anyFloat());

        // Verify StatusBarColorController method invocations.
        InOrder inOrder = Mockito.inOrder(mStatusBarColorController);
        // Invocations before the transition started.
        inOrder.verify(mStatusBarColorController).setTabStripHiddenOnTablet(true);
        inOrder.verify(mStatusBarColorController)
                .setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
        // Invocations during the transition.
        inOrder.verify(mStatusBarColorController).setTabStripHiddenOnTablet(false);
        inOrder.verify(mStatusBarColorController)
                .setTabStripColorOverlay(scrimColor, expectedOpacity);
        // Invocation after the transition finished.
        inOrder.verify(mStatusBarColorController)
                .setTabStripColorOverlay(ScrimProperties.INVALID_COLOR, 0f);
    }

    @Test
    public void testTouchEventsIgnoredOnPaddings() {
        // Update the size and paddings.
        int leftPadding = 10;
        int rightPadding = 20;
        int topPadding = 5;
        int newHeight = TAB_STRIP_HEIGHT_PX + topPadding;
        var appHeaderState =
                new AppHeaderState(
                        new Rect(0, 0, (int) SCREEN_WIDTH, (int) SCREEN_HEIGHT),
                        new Rect(
                                leftPadding,
                                0,
                                (int) (SCREEN_WIDTH - rightPadding),
                                TAB_STRIP_HEIGHT_PX),
                        true);
        mStripLayoutHelperManager.onAppHeaderStateChanged(appHeaderState);
        mStripLayoutHelperManager.onHeightChanged(newHeight);
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);

        float yCenterOfStrip = newHeight / 2;
        assertFalse("Event on paddings should be ignored.", motionEventHandled(0, yCenterOfStrip));
        assertFalse("Event on paddings should be ignored.", motionEventHandled(1, yCenterOfStrip));
        assertFalse(
                "Event on margins should be ignored.",
                motionEventHandled(leftPadding - 1, yCenterOfStrip));
        assertTrue(
                "Event not on margins should be handled.",
                motionEventHandled(leftPadding, yCenterOfStrip));

        assertFalse(
                "Event on margins should be ignored.",
                motionEventHandled(SCREEN_WIDTH, yCenterOfStrip));
        assertFalse(
                "Event on margins should be ignored.",
                motionEventHandled(SCREEN_WIDTH - 1, yCenterOfStrip));
        assertFalse(
                "Event on margins should be ignored.",
                motionEventHandled(SCREEN_WIDTH - rightPadding, yCenterOfStrip));
        assertTrue(
                "Event not on margins should be handled.",
                motionEventHandled(SCREEN_WIDTH - rightPadding - 1, yCenterOfStrip));
    }

    @Test
    public void testTopPadding() {
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();
        int topPadding = 10;
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX + topPadding);
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);

        assertFalse(
                "Event on top padding should not be handled.",
                motionEventHandled(SCREEN_WIDTH / 2, 0));
        assertFalse(
                "Event on top padding should not be handled.",
                motionEventHandled(SCREEN_WIDTH / 2, topPadding - 1));
        assertTrue(
                "Event should be handled below top padding.",
                motionEventHandled(SCREEN_WIDTH / 2, topPadding));
        assertTrue(
                "Ensure top padding increase the entire height",
                motionEventHandled(SCREEN_WIDTH / 2, topPadding + TAB_STRIP_HEIGHT_PX - 1));
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void testUpdateTouchableAreas_WithModelSelectorButton_StripVisible() {
        doTestUpdateTouchableAreas_WithModelSelectorButton(/* showStrip= */ true);
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void testUpdateTouchableAreas_WithModelSelectorButton_StripInvisible() {
        doTestUpdateTouchableAreas_WithModelSelectorButton(/* showStrip= */ false);
    }

    private void doTestUpdateTouchableAreas_WithModelSelectorButton(boolean showStrip) {
        int leftPadding = 10;
        int rightPadding = 20;
        int topPadding = 5;
        var appHeaderState =
                new AppHeaderState(
                        new Rect(0, 0, (int) SCREEN_WIDTH, (int) SCREEN_HEIGHT),
                        new Rect(
                                leftPadding,
                                0,
                                (int) (SCREEN_WIDTH - rightPadding),
                                TAB_STRIP_HEIGHT_PX + topPadding),
                        true);
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();

        // Ensure incognito icon is showing.
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(true);

        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);
        mStripLayoutHelperManager.onAppHeaderStateChanged(appHeaderState);
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX + topPadding);

        float startOpacity = showStrip ? 1f : 0f;
        float endOpacity = showStrip ? 0f : 1f;
        mStripLayoutHelperManager.onFadeTransitionRequested(
                startOpacity, endOpacity, FADE_TRANSITION_DURATION_MS);

        mStripLayoutHelperManager.updateOverlay(0, 0);

        verify(mToolbarContainerView)
                .setSystemGestureExclusionRects(mSystemExclusionRectCaptor.capture());

        if (showStrip) {
            assertEquals(
                    "Number of exclusion rects is wrong.",
                    2,
                    mSystemExclusionRectCaptor.getValue().size());

            Rect rect = mSystemExclusionRectCaptor.getValue().get(0);
            assertEquals("rect.top should be the top padding of the strip.", topPadding, rect.top);
            assertEquals(
                    "rect.bottom should be the height of the strip.",
                    TAB_STRIP_HEIGHT_PX + topPadding,
                    rect.bottom);

            Rect rect2 = mSystemExclusionRectCaptor.getValue().get(1);
            // Left: 728 = width(800) - rightPadding(20) - modelSelectorWidth(32) - endPadding(8) -
            // clickSlop(12)
            // Top: 5 = max(topPadding(5) , topPadding(5) + modelSelectorYOffset(3) -
            // clickSlop(12)))
            // Right: 784 =  width(800) - rightPadding(20) - endPadding(8) + clickSlop(12)
            // Bottom: 45 = min(height(45),  topPadding(5) + modelSelectorHeight(32) +
            // clickSlop(12))
            assertEquals(
                    "2nd rect should represent model selector button.",
                    new Rect(728, 5, 784, 45),
                    rect2);
        } else {
            assertEquals(
                    "Number of exclusion rects is wrong.",
                    1,
                    mSystemExclusionRectCaptor.getValue().size());

            Rect rect = mSystemExclusionRectCaptor.getValue().get(0);
            assertEquals("rect.left should be 0.", 0, rect.left);
            assertEquals("rect.top should be 0.", 0, rect.top);
            assertEquals("rect.right should be 0.", 0, rect.right);
            assertEquals("rect.bottom should be 0.", 0, rect.bottom);
        }
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void testUpdateTouchableAreas_NoModelSelectorButton() {
        int leftPadding = 10;
        int rightPadding = 20;
        int topPadding = 5;
        var appHeaderState =
                new AppHeaderState(
                        new Rect(0, 0, (int) SCREEN_WIDTH, (int) SCREEN_HEIGHT),
                        new Rect(
                                leftPadding,
                                0,
                                (int) (SCREEN_WIDTH - rightPadding),
                                TAB_STRIP_HEIGHT_PX + topPadding),
                        true);
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();

        // Ensure incognito icon is NOT showing.
        mStripLayoutHelperManager.setModelSelectorButtonVisibleForTesting(false);

        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);
        mStripLayoutHelperManager.onAppHeaderStateChanged(appHeaderState);
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX + topPadding);
        mStripLayoutHelperManager.updateOverlay(0, 0);

        verify(mToolbarContainerView)
                .setSystemGestureExclusionRects(mSystemExclusionRectCaptor.capture());
        assertEquals(
                "Number of exclusion rects is wrong.",
                1,
                mSystemExclusionRectCaptor.getValue().size());

        Rect rect = mSystemExclusionRectCaptor.getValue().get(0);
        assertEquals("rect.top should be the top padding of the strip.", topPadding, rect.top);
        assertEquals(
                "rect.bottom should be the height of the strip.",
                TAB_STRIP_HEIGHT_PX + topPadding,
                rect.bottom);
    }

    @Test
    public void testResizeDesktopWindow() {
        // Initially resize the window to hide the strip by triggering the fade transition.
        resizeDesktopWindowAndTriggerFadeTransition(/* showStrip= */ false);
        // Simulate a size change that keeps the strip invisible without re-triggering the fade
        // transition.
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH - 1, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);
        // Verify that a motion event on the strip is still not handled.
        assertFalse(
                "Strip motion event should not be handled.",
                motionEventHandled(SCREEN_WIDTH / 2, TAB_STRIP_HEIGHT_PX / 2f));

        // Resize the window to show the strip by triggering the fade transition.
        resizeDesktopWindowAndTriggerFadeTransition(/* showStrip= */ true);
        // Simulate a size change that keeps the strip visible without re-triggering the fade
        // transition.
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH + 1, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);
        // Verify that a motion event on the strip is still handled.
        assertTrue(
                "Strip motion event should be handled.",
                motionEventHandled(SCREEN_WIDTH / 2, TAB_STRIP_HEIGHT_PX / 2f));
    }

    @Test
    @EnableFeatures(ChromeFeatureList.TAB_STRIP_INCOGNITO_MIGRATION)
    public void testIncognitoSwitcherDisabled() {
        initializeTest();
        assertNull(
                "Incognto switcher button should not be created.",
                mStripLayoutHelperManager.getModelSelectorButton());
    }

    private void resizeDesktopWindowAndTriggerFadeTransition(boolean showStrip) {
        // Rerun initialization after setting the FF.
        ToolbarFeatures.setIsTabStripLayoutOptimizationEnabledForTesting(true);
        initializeTest();

        int leftPadding = 10;
        int rightPadding = 20;
        int topPadding = 5;
        // Simulate the |mTopPadding| update when switching to a desktop window.
        mStripLayoutHelperManager.onHeightChanged(TAB_STRIP_HEIGHT_PX + topPadding);
        // Simulate a window size change in a desktop window.
        var appHeaderState =
                new AppHeaderState(
                        new Rect(0, 0, (int) SCREEN_WIDTH, (int) SCREEN_HEIGHT),
                        new Rect(
                                leftPadding,
                                0,
                                (int) (SCREEN_WIDTH - rightPadding),
                                TAB_STRIP_HEIGHT_PX + topPadding),
                        true);
        float startOpacity = showStrip ? 1f : 0f;
        float endOpacity = showStrip ? 0f : 1f;
        mStripLayoutHelperManager.onAppHeaderStateChanged(appHeaderState);
        mStripLayoutHelperManager.onSizeChanged(
                SCREEN_WIDTH, SCREEN_HEIGHT, VISIBLE_VIEWPORT_Y, ORIENTATION);
        mStripLayoutHelperManager.onFadeTransitionRequested(startOpacity, endOpacity, 0);

        var expectedVisibilityState =
                showStrip ? StripVisibilityState.VISIBLE : StripVisibilityState.INVISIBLE;
        assertEquals(
                "Strip visibility after fade transition is incorrect.",
                expectedVisibilityState,
                mStripLayoutHelperManager.getStripVisibilityState());
        // Verify that the correct rect is set in the motion event filter.
        RectF motionEventFilterArea =
                ((AreaMotionEventFilter) mStripLayoutHelperManager.getEventFilter())
                        .getEventAreaForTesting();
        // Motion event area should be an empty rect on an invisible strip.
        var expectedMotionEventArea =
                showStrip
                        ? new RectF(
                                leftPadding,
                                topPadding,
                                SCREEN_WIDTH - rightPadding,
                                TAB_STRIP_HEIGHT_PX + topPadding)
                        : new RectF();
        assertEquals(
                "Motion event filter area is incorrect.",
                expectedMotionEventArea,
                motionEventFilterArea);
        var yCenterOfStrip = TAB_STRIP_HEIGHT_PX / 2;
        // Verify that a motion event is handled or not, based on the strip visibility state.
        assertEquals(
                "Strip motion event handling based on strip visibility state is incorrect.",
                showStrip,
                motionEventHandled(leftPadding, yCenterOfStrip));
    }

    private boolean motionEventHandled(float x, float y) {
        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, x, y, 0);
        return mStripLayoutHelperManager.getEventFilter().onInterceptTouchEvent(event, false);
    }
}