// 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.customtabs.features.partialcustomtab;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_LAYOUT_STATE_FULL_SCREEN;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_LAYOUT_STATE_SIDE_SHEET;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_TOP;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE;
import static org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_POSITION_DEFAULT;
import static org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_POSITION_END;
import static org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_POSITION_START;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.DEVICE_HEIGHT;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.DEVICE_HEIGHT_LANDSCAPE;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.DEVICE_WIDTH;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.DEVICE_WIDTH_LANDSCAPE;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.DEVICE_WIDTH_MEDIUM;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.FULL_HEIGHT;
import static org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabTestRule.NAVBAR_HEIGHT;

import android.os.Build;
import android.view.View;
import android.view.WindowManager;

import androidx.annotation.Px;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;

import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabBaseStrategy.ResizeType;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.ui.base.LocalizationUtils;

import java.util.concurrent.atomic.AtomicBoolean;

/** Tests for {@link PartialCustomTabSideSheetStrategy}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, shadows = {PartialCustomTabTestRule.ShadowSemanticColorUtils.class})
@LooperMode(Mode.PAUSED)
@EnableFeatures({ChromeFeatureList.CCT_RESIZABLE_SIDE_SHEET})
public class PartialCustomTabSideSheetStrategyTest {
    private static final float MINIMAL_WIDTH_RATIO_EXPANDED = 0.33f;
    private static final float MINIMAL_WIDTH_RATIO_MEDIUM = 0.5f;
    private static final boolean RTL = true;
    private static final boolean LTR = false;
    private static final boolean RIGHT = true;
    private static final boolean LEFT = false;
    private boolean mFullscreen;

    @Rule
    public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();

    @Rule
    public final PartialCustomTabTestRule mPCCTTestRule = new PartialCustomTabTestRule();

    private PartialCustomTabSideSheetStrategy createPcctSideSheetStrategy(@Px int widthPx) {
        return createPcctSideSheetStrategy(widthPx, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);
    }

    private PartialCustomTabSideSheetStrategy createLeftPcctSideSheetStrategy(
            @Px int widthPx, int roundedCornersPosition) {
        return createPcctSideSheetStrategy(widthPx, ACTIVITY_SIDE_SHEET_POSITION_START,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW, roundedCornersPosition);
    }

    private PartialCustomTabSideSheetStrategy createPcctSideSheetStrategy(
            @Px int widthPx, int position) {
        return createPcctSideSheetStrategy(widthPx, position,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);
    }

    private PartialCustomTabSideSheetStrategy createPcctSideSheetStrategy(
            @Px int widthPx, int position, int decorationType, int roundedCornersPosition) {
        PartialCustomTabSideSheetStrategy pcct = new PartialCustomTabSideSheetStrategy(
                mPCCTTestRule.mActivity, widthPx, mPCCTTestRule.mOnResizedCallback,
                mPCCTTestRule.mOnActivityLayoutCallback, mPCCTTestRule.mFullscreenManager, false,
                true, /*showMaximizedButton=*/true,
                /*startMaximized=*/false, position, ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE,
                mPCCTTestRule.mHandleStrategyFactory, decorationType, roundedCornersPosition);
        pcct.setMockViewForTesting(mPCCTTestRule.mCoordinatorLayout, mPCCTTestRule.mToolbarView,
                mPCCTTestRule.mToolbarCoordinator);
        return pcct;
    }

    @Test
    @Config(sdk = Build.VERSION_CODES.Q)
    public void create_sideSheetStrategy_Q() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabSideSheetStrategy strategy = createPcctSideSheetStrategy(2000);

        assertEquals("Side-Sheet PCCT should be created",
                PartialCustomTabBaseStrategy.PartialCustomTabType.SIDE_SHEET,
                strategy.getStrategyType());
        assertEquals(
                "Side-sheet has wrong width", 2000, mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void create_sideSheetStrategy() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabSideSheetStrategy strategy = createPcctSideSheetStrategy(2000);

        assertEquals("Side-Sheet PCCT should be created",
                PartialCustomTabBaseStrategy.PartialCustomTabType.SIDE_SHEET,
                strategy.getStrategyType());
        assertEquals(
                "Side-sheet has wrong width", 2000, mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void create_largeWidthLandscape() {
        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(5000);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertTabIsAtFullLandscapeHeight();
        assertEquals("Side-sheet has wrong width", DEVICE_WIDTH_LANDSCAPE,
                mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void create_smallWidthLandscape_Expanded() {
        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(100);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertTabIsAtFullLandscapeHeight();
        assertEquals("Side-sheet has wrong width",
                (int) (DEVICE_WIDTH_LANDSCAPE * MINIMAL_WIDTH_RATIO_EXPANDED),
                mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void create_smallWidthLandscape_Medium() {
        mPCCTTestRule.configDeviceWidthMedium();
        createPcctSideSheetStrategy(100);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertEquals("Side-sheet has wrong width",
                (int) (DEVICE_WIDTH_MEDIUM * MINIMAL_WIDTH_RATIO_MEDIUM),
                mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void create_deviceWidthLandscape() {
        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(DEVICE_WIDTH_LANDSCAPE);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertTabIsAtFullLandscapeHeight();
        assertEquals("Side-sheet has wrong width", DEVICE_WIDTH_LANDSCAPE,
                mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void create_widthHalfWindowLandscape() {
        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(DEVICE_WIDTH_LANDSCAPE / 2);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertEquals(
                "Should only have one attribute result", 1, mPCCTTestRule.mAttributeResults.size());
        assertTabIsAtFullLandscapeHeight();
        assertEquals(DEVICE_WIDTH_LANDSCAPE / 2, mPCCTTestRule.mAttributeResults.get(0).width);
    }

    @Test
    public void onShowSoftInputRunsRunnable() {
        mPCCTTestRule.configLandscapeMode();
        var strategy = createPcctSideSheetStrategy(2000);
        AtomicBoolean hasRunnableRun = new AtomicBoolean(false);

        strategy.onShowSoftInput(() -> { hasRunnableRun.set(true); });

        assertEquals("The runnable should be run", true, hasRunnableRun.get());
        assertTabIsAtFullLandscapeHeight();
    }

    @Test
    public void dragHandlebarInvisible() {
        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(2000);

        mPCCTTestRule.verifyWindowFlagsSet();
        assertEquals(1, mPCCTTestRule.mAttributeResults.size());
        assertTabIsAtFullLandscapeHeight();
        assertEquals(
                "Side-sheet has wrong width", 2000, mPCCTTestRule.mAttributeResults.get(0).width);
        verify(mPCCTTestRule.mDragHandlebar).setVisibility(View.GONE);
    }

    @Test
    public void noTopShadow() {
        doReturn(47)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));

        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(2000);

        mPCCTTestRule.verifyWindowFlagsSet();
        assertTabIsAtFullLandscapeHeight();
        assertEquals(
                "Side-sheet has wrong width", 2000, mPCCTTestRule.mAttributeResults.get(0).width);
        assertEquals("Top margin should be zero for the shadow", 0,
                mPCCTTestRule.mLayoutParams.topMargin);
    }

    @Config(sdk = Build.VERSION_CODES.Q)
    @Test
    public void leftShadowIsVisible() {
        doReturn(47)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));

        mPCCTTestRule.configLandscapeMode();
        createPcctSideSheetStrategy(2000);

        mPCCTTestRule.verifyWindowFlagsSet();
        assertTabIsAtFullLandscapeHeight();
        assertEquals(
                "Side-sheet has wrong width", 2000, mPCCTTestRule.mAttributeResults.get(0).width);
        assertNotEquals("Left margin should be non-zero for the shadow", 0,
                mPCCTTestRule.mLayoutParams.leftMargin);
        assertEquals("Right margin should be zero because shadow is on left side", 0,
                mPCCTTestRule.mLayoutParams.rightMargin);
    }

    @Config(sdk = Build.VERSION_CODES.Q)
    @Test
    public void rightShadowIsVisible() {
        doReturn(47)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));

        mPCCTTestRule.configLandscapeMode();
        createLeftPcctSideSheetStrategy(2000, ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);

        mPCCTTestRule.verifyWindowFlagsSet();
        assertTabIsAtFullLandscapeHeight();
        assertEquals(
                "Side-sheet has wrong width", 2000, mPCCTTestRule.mAttributeResults.get(0).width);
        assertNotEquals("Right margin should be non-zero for the shadow", 0,
                mPCCTTestRule.mLayoutParams.rightMargin);
        assertEquals("Left margin should be zero because shadow is on right side", 0,
                mPCCTTestRule.mLayoutParams.leftMargin);
    }

    @Test
    public void roundTopLeftCorner() {
        doReturn(16)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_handle_height));
        mPCCTTestRule.configLandscapeMode();
        var strategy = createPcctSideSheetStrategy(2000, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_TOP);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);

        assertTabIsAtFullLandscapeHeight();
        assertNotEquals("Drag bar not rounded", 0, mPCCTTestRule.mDragBarLayoutParams.height);
        verify(mPCCTTestRule.mDragBarBackground, times(2))
                .setCornerRadii(eq(new float[] {5, 5, 0, 0, 0, 0, 0, 0}));
    }

    @Test
    public void roundTopRightCorner() {
        doReturn(16)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_handle_height));
        mPCCTTestRule.configLandscapeMode();
        var strategy =
                createLeftPcctSideSheetStrategy(2000, ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_TOP);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);

        assertTabIsAtFullLandscapeHeight();
        assertNotEquals("Drag bar not rounded", 0, mPCCTTestRule.mDragBarLayoutParams.height);
        verify(mPCCTTestRule.mDragBarBackground, times(2))
                .setCornerRadii(eq(new float[] {0, 0, 5, 5, 0, 0, 0, 0}));
    }

    @Test
    public void noRoundedCorner() {
        mPCCTTestRule.configLandscapeMode();
        var strategy = createPcctSideSheetStrategy(2000);

        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);

        assertTabIsAtFullLandscapeHeight();
        assertEquals("Drag bar rounded", 0, mPCCTTestRule.mDragBarLayoutParams.height);
        verify(mPCCTTestRule.mDragBarBackground, times(2))
                .setCornerRadii(eq(new float[] {0, 0, 0, 0, 0, 0, 0, 0}));
    }

    @Test
    public void noShadowsFullWidth() {
        doReturn(47)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));

        mPCCTTestRule.configLandscapeMode();
        createLeftPcctSideSheetStrategy(3000, ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);

        mPCCTTestRule.verifyWindowFlagsSet();
        assertTabIsAtFullLandscapeHeight();
        assertEquals("Side-sheet has wrong width", DEVICE_WIDTH_LANDSCAPE,
                mPCCTTestRule.mRealMetrics.widthPixels);
        assertEquals("Right margin should be zero because side sheet is max width", 0,
                mPCCTTestRule.mLayoutParams.rightMargin);

        mPCCTTestRule.configPortraitMode();
        createPcctSideSheetStrategy(2000);
        assertEquals(
                "Side-sheet has wrong width", DEVICE_WIDTH, mPCCTTestRule.mRealMetrics.widthPixels);
        assertEquals("Left margin should be zero because side sheet is max width", 0,
                mPCCTTestRule.mLayoutParams.leftMargin);
    }

    @Test
    public void drawDividerLine() {
        doReturn(10)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));
        mPCCTTestRule.configLandscapeMode();
        var strategy = createPcctSideSheetStrategy(2000, ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertTrue(strategy.shouldDrawDividerLine());
        assertEquals("Right margin should zero for no shadow", 0,
                mPCCTTestRule.mLayoutParams.rightMargin);
        assertEquals("Left margin should be zero for no shadow", 0,
                mPCCTTestRule.mLayoutParams.leftMargin);
    }

    @Config(sdk = Build.VERSION_CODES.P)
    @Test
    public void drawDividerLine_OverrideWithOldOS() {
        doReturn(10)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));
        mPCCTTestRule.configLandscapeMode();
        // Only override instead of shadow. If they set to no decoration, we don't override with
        // divider line
        var strategy = createPcctSideSheetStrategy(2000, ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertTrue(strategy.shouldDrawDividerLine());
        assertEquals("Right margin should zero for no shadow", 0,
                mPCCTTestRule.mLayoutParams.rightMargin);
        assertEquals("Left margin should be zero for no shadow", 0,
                mPCCTTestRule.mLayoutParams.leftMargin);
    }

    @Test
    public void noDecoration() {
        doReturn(10)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));
        mPCCTTestRule.configLandscapeMode();
        var strategy = createPcctSideSheetStrategy(2000, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_NONE, ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        mPCCTTestRule.verifyWindowFlagsSet();

        assertFalse(strategy.shouldDrawDividerLine());
        assertEquals("Right margin should zero for no shadow", 0,
                mPCCTTestRule.mLayoutParams.rightMargin);
        assertEquals("Left margin should be zero for no shadow", 0,
                mPCCTTestRule.mLayoutParams.leftMargin);
    }

    @Config(sdk = Build.VERSION_CODES.Q)
    @Test
    public void enterAndExitHtmlFullscreen() {
        doReturn(47)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_shadow_offset));
        doReturn(10)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_handle_height));
        var strategy = createPcctSideSheetStrategy(1000, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_TOP);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        assertFalse(getWindowAttributes().isFullscreen());
        int height = getWindowAttributes().height;
        int width = getWindowAttributes().width;
        mPCCTTestRule.verifyWindowFlagsSet();

        strategy.setFullscreenSupplierForTesting(() -> mFullscreen);

        mFullscreen = true;
        strategy.onEnterFullscreen(null, null);
        assertTrue(getWindowAttributes().isFullscreen());
        assertEquals("Shadow should be removed.", 0, mPCCTTestRule.mLayoutParams.leftMargin);
        assertEquals("Toolbar still present.", 0, mPCCTTestRule.mLayoutParams.topMargin);
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(DEVICE_HEIGHT), eq(DEVICE_WIDTH));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(0), eq(0), eq(DEVICE_WIDTH), eq(DEVICE_HEIGHT),
                        eq(ACTIVITY_LAYOUT_STATE_FULL_SCREEN));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        mFullscreen = false;
        strategy.onExitFullscreen(null);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertFalse(getWindowAttributes().isFullscreen());
        assertEquals(height, getWindowAttributes().height);
        assertEquals(width, getWindowAttributes().width);
        assertNotEquals("Shadow should be restored.", 0, mPCCTTestRule.mLayoutParams.leftMargin);
        assertNotEquals("Toolbar should be restored.", 0, mPCCTTestRule.mLayoutParams.topMargin);
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(height), eq(width));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(DEVICE_WIDTH - width), eq(0), eq(DEVICE_WIDTH),
                        eq(DEVICE_HEIGHT - NAVBAR_HEIGHT), eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void enterAndExitHtmlFullscreen_useDivider() {
        doReturn(10)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_handle_height));
        var strategy = createPcctSideSheetStrategy(800, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_TOP);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        mPCCTTestRule.configInsetDrawableBg();
        assertFalse(getWindowAttributes().isFullscreen());
        int height = getWindowAttributes().height;
        int width = getWindowAttributes().width;
        mPCCTTestRule.verifyWindowFlagsSet();

        strategy.setFullscreenSupplierForTesting(() -> mFullscreen);

        mFullscreen = true;
        strategy.onEnterFullscreen(null, null);
        assertTrue(getWindowAttributes().isFullscreen());
        assertEquals("Toolbar still present.", 0, mPCCTTestRule.mLayoutParams.topMargin);
        // this line gets called when divider line is removed
        verify(mPCCTTestRule.mCoordinatorLayout).getPaddingRight();
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(DEVICE_HEIGHT), eq(DEVICE_WIDTH));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(0), eq(0), eq(DEVICE_WIDTH), eq(DEVICE_HEIGHT),
                        eq(ACTIVITY_LAYOUT_STATE_FULL_SCREEN));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        mFullscreen = false;
        strategy.onExitFullscreen(null);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertFalse(getWindowAttributes().isFullscreen());
        assertNotEquals("Toolbar should be restored.", 0, mPCCTTestRule.mLayoutParams.topMargin);
        assertEquals(height, getWindowAttributes().height);
        assertEquals(width, getWindowAttributes().width);
        // called twice because divider line is restored
        verify(mPCCTTestRule.mDragBar, times(2)).setBackground(any());
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(height), eq(width));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(DEVICE_WIDTH - width), eq(0), eq(DEVICE_WIDTH),
                        eq(DEVICE_HEIGHT - NAVBAR_HEIGHT), eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void enterAndExitMaximizeMode() {
        var strategy = createPcctSideSheetStrategy(700);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        assertFalse(getWindowAttributes().isFullscreen());
        int height = getWindowAttributes().height;
        int width = getWindowAttributes().width;

        strategy.toggleMaximize(true);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(FULL_HEIGHT), eq(DEVICE_WIDTH));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(0), eq(0), eq(DEVICE_WIDTH), eq(DEVICE_HEIGHT - NAVBAR_HEIGHT),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        strategy.toggleMaximize(true);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertEquals(height, getWindowAttributes().height);
        assertEquals(width, getWindowAttributes().width);
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(height), eq(width));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(DEVICE_WIDTH - width), eq(0), eq(DEVICE_WIDTH),
                        eq(DEVICE_HEIGHT - NAVBAR_HEIGHT), eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void toggleMaximizeNoAnimation() {
        var strategy = createPcctSideSheetStrategy(700);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        int height = getWindowAttributes().height;
        int width = getWindowAttributes().width;
        strategy.toggleMaximize(/*animation=*/false);
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(FULL_HEIGHT), eq(DEVICE_WIDTH));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(0), eq(0), eq(DEVICE_WIDTH), eq(DEVICE_HEIGHT - NAVBAR_HEIGHT),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        strategy.toggleMaximize(/*animation=*/false);
        // Even without animation, we still need to wait for task to be idle, since we post
        // the task for size init in |onMaximizeEnd|.
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertEquals(height, getWindowAttributes().height);
        assertEquals(width, getWindowAttributes().width);
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(height), eq(width));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(DEVICE_WIDTH - width), eq(0), eq(DEVICE_WIDTH),
                        eq(DEVICE_HEIGHT - NAVBAR_HEIGHT), eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void maximizeAndFullscreen() {
        doReturn(16)
                .when(mPCCTTestRule.mResources)
                .getDimensionPixelSize(eq(R.dimen.custom_tabs_handle_height));
        // Ensure maximize -> fullscreen enter/exit -> comes back to maximize mode
        var strategy = createPcctSideSheetStrategy(700, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW,
                ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_TOP);
        strategy.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        assertNotEquals("Corner not rounded", 0, mPCCTTestRule.mLayoutParams.topMargin);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        strategy.setFullscreenSupplierForTesting(() -> mFullscreen);
        strategy.toggleMaximize(true);

        PartialCustomTabTestRule.waitForAnimationToFinish();
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(FULL_HEIGHT), eq(DEVICE_WIDTH));
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(0), eq(0), eq(DEVICE_WIDTH), eq(DEVICE_HEIGHT - NAVBAR_HEIGHT),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED));
        assertEquals("Corner rounded while maximized", 0, mPCCTTestRule.mLayoutParams.topMargin);

        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        mFullscreen = true;
        strategy.onEnterFullscreen(null, null);
        assertEquals("Corner rounded in fullscreen", 0, mPCCTTestRule.mLayoutParams.topMargin);
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        // Verify we get a single resize callback invocation when exiting fullscreen and restoring
        // maximize mode.
        mFullscreen = false;
        strategy.onExitFullscreen(null);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertEquals("Corner rounded while maximized", 0, mPCCTTestRule.mLayoutParams.topMargin);
        verify(mPCCTTestRule.mOnResizedCallback).onResized(eq(FULL_HEIGHT), eq(DEVICE_WIDTH));
        clearInvocations(mPCCTTestRule.mOnResizedCallback);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(eq(0), eq(0), eq(DEVICE_WIDTH), eq(DEVICE_HEIGHT - NAVBAR_HEIGHT),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void sheetPosition() {
        assertPosition(LEFT, RTL, ACTIVITY_SIDE_SHEET_POSITION_DEFAULT);
        assertPosition(LEFT, RTL, ACTIVITY_SIDE_SHEET_POSITION_END);
        assertPosition(RIGHT, RTL, ACTIVITY_SIDE_SHEET_POSITION_START);
        assertPosition(RIGHT, LTR, ACTIVITY_SIDE_SHEET_POSITION_DEFAULT);
        assertPosition(RIGHT, LTR, ACTIVITY_SIDE_SHEET_POSITION_END);
        assertPosition(LEFT, LTR, ACTIVITY_SIDE_SHEET_POSITION_START);
    }

    @Test
    public void handleCloseAnimation() {
        var strategy = createPcctSideSheetStrategy(2000);
        strategy.setSheetOnRightForTesting(true);
        var invoked = new ObservableSupplierImpl<Boolean>();

        invoked.set(false);
        assertEquals(0, mPCCTTestRule.getWindowAttributes().x);
        strategy.handleCloseAnimation(() -> invoked.set(true)); // Slide out to right
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertTrue(invoked.get());
        assertEquals(DEVICE_WIDTH, mPCCTTestRule.getWindowAttributes().x);

        invoked.set(false);
        strategy = createPcctSideSheetStrategy(2000);
        strategy.setSheetOnRightForTesting(false);
        strategy.setSlideDownAnimationForTesting(true); // Slide down
        strategy.handleCloseAnimation(() -> invoked.set(true));
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertTrue(invoked.get());
        assertEquals(DEVICE_HEIGHT, mPCCTTestRule.getWindowAttributes().y);
    }
    @Test
    public void maximizeMinimize() {
        mPCCTTestRule.configLandscapeMode();
        var strategy = createPcctSideSheetStrategy(700);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        int expected = ResizeType.MANUAL_EXPANSION;
        var histogramExpansion =
                HistogramWatcher.newSingleRecordWatcher("CustomTabs.SideSheetResizeType", expected);
        strategy.toggleMaximize(true);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertTrue("Should be in maximized state.", strategy.isMaximized());
        histogramExpansion.assertExpected("ResizeType.MANUAL_EXPANSION should be recorded once.");

        expected = PartialCustomTabBottomSheetStrategy.ResizeType.MANUAL_MINIMIZATION;
        var histogramMinimization =
                HistogramWatcher.newSingleRecordWatcher("CustomTabs.SideSheetResizeType", expected);
        strategy.toggleMaximize(true);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertFalse("Should be in minimized state.", strategy.isMaximized());
        histogramMinimization.assertExpected(
                "ResizeType.MANUAL_MINIMIZATION should be recorded once.");
    }

    private static void assertPosition(boolean isRightSide, boolean isRtl, int position) {
        LocalizationUtils.setRtlForTesting(isRtl);
        String msg = isRightSide ? "Should be on right" : "Should be on left";
        assertEquals(msg, isRightSide, PartialCustomTabSideSheetStrategy.isSheetOnRight(position));
    }

    private void assertTabIsAtFullLandscapeHeight() {
        assertEquals(
                "Should only have one attribute result", 1, mPCCTTestRule.mAttributeResults.size());
        assertEquals(DEVICE_HEIGHT_LANDSCAPE, mPCCTTestRule.mAttributeResults.get(0).height);
    }

    private WindowManager.LayoutParams getWindowAttributes() {
        return mPCCTTestRule.getWindowAttributes();
    }
}
