// 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.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.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET;
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_SHADOW;
import static org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider.ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE;
import static org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_POSITION_END;
import static org.chromium.chrome.browser.customtabs.CustomTabIntentDataProvider.ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE;

import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Build;

import androidx.annotation.Px;

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

import org.chromium.base.supplier.Supplier;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.util.HistogramWatcher;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.browserservices.intents.BrowserServicesIntentDataProvider;
import org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabBaseStrategy.PartialCustomTabType;
import org.chromium.chrome.browser.customtabs.features.partialcustomtab.PartialCustomTabDisplayManager.SizeStrategyCreator;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.multiwindow.MultiWindowUtils;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.ui.base.LocalizationUtils;

/** Tests for {@link PartialCustomTabDisplayManager}. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, shadows = {PartialCustomTabTestRule.ShadowSemanticColorUtils.class})
@LooperMode(Mode.PAUSED)
@EnableFeatures({ChromeFeatureList.CCT_RESIZABLE_SIDE_SHEET,
        ChromeFeatureList.CCT_RESIZABLE_SIDE_SHEET_FOR_THIRD_PARTIES})
public class PartialCustomTabDisplayManagerTest {
    private static final int BOTTOM_SHEET_MAX_WIDTH_DP = 900;

    private boolean mFullscreen;
    @Rule
    public TestRule mFeaturesProcessorRule = new Features.JUnitProcessor();
    @Rule
    public final PartialCustomTabTestRule mPCCTTestRule = new PartialCustomTabTestRule();

    private PartialCustomTabDisplayManager createPcctDisplayManager() {
        return createPcctDisplayManager(800, 2000);
    }

    private PartialCustomTabDisplayManager createPcctDisplayManager(
            @Px int heightPx, @Px int widthPx) {
        return createPcctDisplayManager(
                heightPx, widthPx, 1850, ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW);
    }

    private PartialCustomTabDisplayManager createPcctDisplayManager(
            @Px int heightPx, @Px int widthPx, int breakPointDp, int decorationType) {
        PartialCustomTabDisplayManager displayManager = new PartialCustomTabDisplayManager(
                mPCCTTestRule.mActivity, heightPx, widthPx, breakPointDp, false,
                mPCCTTestRule.mOnResizedCallback, mPCCTTestRule.mOnActivityLayoutCallback,
                mPCCTTestRule.mActivityLifecycleDispatcher, mPCCTTestRule.mFullscreenManager, false,
                true, /*showMaximizeButton=*/true, decorationType, ACTIVITY_SIDE_SHEET_POSITION_END,
                ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE, ACTIVITY_SIDE_SHEET_ROUNDED_CORNERS_NONE);
        var sizeStrategyCreator = displayManager.getSizeStrategyCreatorForTesting();
        SizeStrategyCreator testSizeStrategyCreator =
                (type, maximized, sideSheetPosition, sideSheetAnimation) -> {
            var strategy = sizeStrategyCreator.createForType(
                    type, maximized, sideSheetPosition, sideSheetAnimation);
            strategy.setFullscreenSupplierForTesting(() -> mFullscreen);
            strategy.setMockViewForTesting(mPCCTTestRule.mCoordinatorLayout,
                    mPCCTTestRule.mToolbarView, mPCCTTestRule.mToolbarCoordinator);
            return strategy;
        };
        displayManager.setMocksForTesting(mPCCTTestRule.mCoordinatorLayout,
                mPCCTTestRule.mToolbarView, mPCCTTestRule.mToolbarCoordinator,
                mPCCTTestRule.mHandleStrategyFactory, testSizeStrategyCreator);
        return displayManager;
    }

    @Test
    public void create_FullSize_HeightNotSetWidthNotSet() {
        int expected = PartialCustomTabType.FULL_SIZE;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager(0, 0);
        assertEquals("Full-Size PCCT should be created", PartialCustomTabType.FULL_SIZE,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.FULL_SIZE should be recorded once");
    }

    @Test
    public void create_FullSize_WidthSetCompactDevice() {
        int expected = PartialCustomTabType.FULL_SIZE;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        mPCCTTestRule.configCompactDevice();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager(0, 500);
        assertEquals("Full-Size PCCT should be created", PartialCustomTabType.FULL_SIZE,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.FULL_SIZE should be recorded once");
    }

    @Test
    public void create_SideSheet_WidthSetHeightNot_BelowBreakpoint() {
        int expected = PartialCustomTabType.FULL_SIZE;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager(0, 800);
        assertEquals("Full-Size PCCT should be created", PartialCustomTabType.FULL_SIZE,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.FULL_SIZE should be recorded once");
    }

    @Test
    public void create_SideSheet_WidthSetHeightNot_AboveBreakpoint() {
        int expected = PartialCustomTabType.SIDE_SHEET;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        PartialCustomTabDisplayManager displayManager =
                createPcctDisplayManager(0, 2000, 840, ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW);
        assertEquals(
                "Breakpoint value is incorrect", 840, displayManager.getBreakPointDpForTesting());
        assertEquals("Side-Sheet PCCT should be created", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.SIDE_SHEET should be recorded once");
    }

    @Test
    public void create_SideSheet_AboveBreakPoint() {
        int expected = PartialCustomTabType.SIDE_SHEET;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();

        assertEquals("Side-Sheet PCCT should be created", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.SIDE_SHEET should be recorded once");
    }

    @Test
    public void create_BottomSheet_HeightWidthSet_Compact() {
        int expected = PartialCustomTabType.BOTTOM_SHEET;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        mPCCTTestRule.configCompactDevice();
        PartialCustomTabDisplayManager displayManager =
                createPcctDisplayManager(350, 450, 400, ACTIVITY_SIDE_SHEET_DECORATION_TYPE_SHADOW);
        assertEquals(
                "Breakpoint value is incorrect", 600, displayManager.getBreakPointDpForTesting());
        assertEquals("Bottom-Sheet PCCT should be created", PartialCustomTabType.BOTTOM_SHEET,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.BOTTOM_SHEET should be recorded once");
    }

    @Test
    public void create_BottomSheet_HeightSetWidthNot() {
        int expected = PartialCustomTabType.BOTTOM_SHEET;
        var histogram = HistogramWatcher.newSingleRecordWatcher(
                "CustomTabs.PartialCustomTabType", expected);
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager(800, 0);
        assertEquals("Bottom-Sheet PCCT should be created", PartialCustomTabType.BOTTOM_SHEET,
                displayManager.getActiveStrategyType());
        histogram.assertExpected("PartialCustomTabType.BOTTOM_SHEET should be recorded once");
    }

    @Test
    public void create_BottomSheet_BelowBreakPoint() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();

        assertEquals("Bottom-Sheet PCCT should be created", PartialCustomTabType.BOTTOM_SHEET,
                displayManager.getActiveStrategyType());
    }

    @Test
    public void create_BottomSheetStrategy() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();

        assertEquals("Bottom-Sheet PCCT should be created", PartialCustomTabType.BOTTOM_SHEET,
                displayManager.getActiveStrategyType());
    }

    @Test
    public void create_SideSheetStrategy() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();

        assertEquals("Side-Sheet PCCT should be created", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());
    }

    @Test
    public void transitionFromBottomSheetToSideSheetWhenOrientationChangedToLandscape() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        mPCCTTestRule.configLandscapeMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);

        PartialCustomTabTestRule.waitForAnimationToFinish();

        assertEquals("Side-Sheet should be the active strategy", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());

        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void transitionFromBottomSheetToSideSheetWhileSoftkeyboardIsOn() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        displayManager.onShowSoftInput(() -> {});
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertTrue(displayManager.getSizeStrategyForTesting().isMaximized());

        // Rotate while the soft keyboard is on.
        mPCCTTestRule.configLandscapeMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);
        PartialCustomTabTestRule.waitForAnimationToFinish();

        // The destroyed bottom sheet should have its height state back to 'initial'.
        assertFalse(displayManager.getSizeStrategyForTesting().isMaximized());
    }

    @Test
    public void closeAnimationNotInvokedTwice() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();
        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        Runnable finish = Mockito.mock(Runnable.class);
        assertTrue("Close animation didn't run", displayManager.handleCloseAnimation(finish));
        Runnable finish2 = Mockito.mock(Runnable.class);
        assertFalse("Close animation shouldn't run", displayManager.handleCloseAnimation(finish2));

        mPCCTTestRule.configLandscapeMode();
        displayManager = createPcctDisplayManager();
        assertEquals("Side-Sheet should be the active strategy", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());
        assertTrue("Close animation didn't run", displayManager.handleCloseAnimation(finish));
        assertFalse("Close animation shouldn't run", displayManager.handleCloseAnimation(finish2));

        displayManager = createPcctDisplayManager(0, 0);
        assertEquals("Full-Size PCCT should be created", PartialCustomTabType.FULL_SIZE,
                displayManager.getActiveStrategyType());
        assertTrue("Close animation didn't run", displayManager.handleCloseAnimation(finish));
        assertFalse("Close animation shouldn't run", displayManager.handleCloseAnimation(finish2));
    }

    @Test
    public void
    transitionFromBottomSheetTo900dpBottomSheetWhenOrientationChangedToLandscape_andHeightSetWidthNot() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager(800, 0);

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        mPCCTTestRule.configLandscapeMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);

        PartialCustomTabTestRule.waitForAnimationToFinish();

        // the density in this case is 1.0
        assertEquals("Should be 900dp width bottom sheet", BOTTOM_SHEET_MAX_WIDTH_DP,
                (int) (mPCCTTestRule.getWindowAttributes().width));
        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void transitionFromSideSheetToBottomSheetWhenOrientationChangedToPortrait() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();

        assertEquals("Side-Sheet should be the active strategy", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        mPCCTTestRule.configPortraitMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);

        PartialCustomTabTestRule.waitForAnimationToFinish();

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Config(sdk = Build.VERSION_CODES.Q)
    @Test
    public void transitionFromDividerSideSheetToBottomSheetWhenOrientationChangedToPortrait() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager(
                850, 2000, 1850, ACTIVITY_SIDE_SHEET_DECORATION_TYPE_DIVIDER);
        displayManager.onToolbarInitialized(
                mPCCTTestRule.mToolbarCoordinator, mPCCTTestRule.mToolbarView, 5);
        assertEquals("Side-Sheet should be the active strategy", PartialCustomTabType.SIDE_SHEET,
                displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        verify(mPCCTTestRule.mCoordinatorLayout).setBackground(any(Drawable.class));
        mPCCTTestRule.configInsetDrawableBg();
        mPCCTTestRule.configPortraitMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);

        PartialCustomTabTestRule.waitForAnimationToFinish();

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());

        verify(mPCCTTestRule.mCoordinatorLayout).getPaddingRight();
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void dontTransitionIfOrientationDoesNotChange() {
        mPCCTTestRule.configPortraitMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);
        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        verify(mPCCTTestRule.mOnActivityLayoutCallback, never())
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
    }

    @Test
    public void rotateInFullscreenMode() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);
        var attrs = mPCCTTestRule.getWindowAttributes();
        int height = attrs.height;
        int width = attrs.width;

        mFullscreen = true;
        displayManager.getSizeStrategyForTesting().setFullscreenSupplierForTesting(
                () -> mFullscreen);

        displayManager.getSizeStrategyForTesting().onEnterFullscreen(null, null);
        assertTrue(mPCCTTestRule.getWindowAttributes().isFullscreen());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_FULL_SCREEN));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        mPCCTTestRule.configPortraitMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);
        PartialCustomTabTestRule.waitForAnimationToFinish();

        assertEquals("Bottom-Sheet should be the active strategy",
                PartialCustomTabType.BOTTOM_SHEET, displayManager.getActiveStrategyType());
        // Bottom-sheet strategy is now in action. Verify its top margin is removed
        // for the correct fullscreen UI.
        assertEquals("Top margin should be zero in fullscreen", 0,
                displayManager.getSizeStrategyForTesting().getTopMarginForTesting());

        mPCCTTestRule.configLandscapeMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);

        mFullscreen = false;
        displayManager.getSizeStrategyForTesting().onExitFullscreen(null);
        PartialCustomTabTestRule.waitForAnimationToFinish();

        attrs = mPCCTTestRule.getWindowAttributes();
        assertFalse("Should not be in fulllscreen.", attrs.isFullscreen());
        assertEquals("Height should be restored.", height, attrs.height);
        assertEquals("Width should be restored.", width, attrs.width);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void rotateInMaximizeMode() {
        mPCCTTestRule.configLandscapeMode();
        PartialCustomTabDisplayManager displayManager = createPcctDisplayManager();
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        var attrs = mPCCTTestRule.getWindowAttributes();
        int height = attrs.height;
        int width = attrs.width;

        var sideSheetStrategy = displayManager.getSizeStrategyForTesting();
        ((PartialCustomTabSideSheetStrategy) sideSheetStrategy).toggleMaximize(true);
        PartialCustomTabTestRule.waitForAnimationToFinish();
        assertTrue("Should be in maximized state.", sideSheetStrategy.isMaximized());

        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        mPCCTTestRule.configPortraitMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);
        PartialCustomTabTestRule.waitForAnimationToFinish();

        // Strategy is now set to bottom sheet. Verify it starts in initial-height mode regardless
        // of the previous strategy state.
        var bottomSheetStrategy = displayManager.getSizeStrategyForTesting();
        assertFalse(
                "Bottom sheet must start in initial height.", bottomSheetStrategy.isMaximized());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_BOTTOM_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        mPCCTTestRule.configLandscapeMode();
        displayManager.onConfigurationChanged(mPCCTTestRule.mConfiguration);

        sideSheetStrategy = displayManager.getSizeStrategyForTesting();

        // When coming back to SideSheet strategy, the last state should be restored.
        assertTrue("Side sheet must start in minimized state.", sideSheetStrategy.isMaximized());
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET_MAXIMIZED));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);

        ((PartialCustomTabSideSheetStrategy) sideSheetStrategy).toggleMaximize(true);
        PartialCustomTabTestRule.waitForAnimationToFinish();

        attrs = mPCCTTestRule.getWindowAttributes();
        assertFalse("Should not be in maximized state.", sideSheetStrategy.isMaximized());
        assertEquals("Height should be restored.", height, attrs.height);
        assertEquals("Width should be restored.", width, attrs.width);
        verify(mPCCTTestRule.mOnActivityLayoutCallback)
                .onActivityLayout(anyInt(), anyInt(), anyInt(), anyInt(),
                        eq(ACTIVITY_LAYOUT_STATE_SIDE_SHEET));
        clearInvocations(mPCCTTestRule.mOnActivityLayoutCallback);
    }

    @Test
    public void calculatePartialCustomTabTypePermutations() {
        int initWidth = 0;
        int initHeight = 0;
        Supplier<Integer> displayWidthDp = null;
        int breakPointDp = 0;

        // Multi-window mode -> FULL
        MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(true);
        assertEquals("Type should be FULL_SIZE", PartialCustomTabType.FULL_SIZE,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));

        // Zero initial width/height -> FULL
        MultiWindowUtils.getInstance().setIsInMultiWindowModeForTesting(false);
        assertEquals("Type should be FULL_SIZE", PartialCustomTabType.FULL_SIZE,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));

        // Non-zero height -> BOTTOM_SHEET
        initWidth = 0;
        initHeight = 500;
        assertEquals("Type should be BOTTOM_SHEET", PartialCustomTabType.BOTTOM_SHEET,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));

        // Non-zero width -> either FULL_SIZE or SIDE_SHEET
        initWidth = 500;
        initHeight = 0;
        displayWidthDp = () -> 1000;
        breakPointDp = 1200;
        assertEquals("Type should be FULL_SIZE", PartialCustomTabType.FULL_SIZE,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));

        breakPointDp = 800;
        assertEquals("Type should be SIDE_SHEET", PartialCustomTabType.SIDE_SHEET,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));

        // Non-zero width/height -> either SIDE_SHEET or BOTTOM_SHEET
        initWidth = 300;
        initHeight = 500;
        displayWidthDp = () -> 1000;
        breakPointDp = 400;
        assertEquals("Type should be SIDE_SHEET", PartialCustomTabType.SIDE_SHEET,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));

        initWidth = 300;
        initHeight = 500;
        displayWidthDp = () -> 1000;
        breakPointDp = 1200;
        assertEquals("Type should be BOTTOM_SHEET", PartialCustomTabType.BOTTOM_SHEET,
                PartialCustomTabDisplayManager.calculatePartialCustomTabType(
                        null, initWidth, initHeight, displayWidthDp, breakPointDp));
    }

    @Test
    public void startAnimationOverride() {
        int defId = 42;
        var provider = Mockito.mock(BrowserServicesIntentDataProvider.class);
        when(provider.getAnimationEnterRes()).thenReturn(defId);

        // FULL_SIZE -> slide up
        assertEquals(R.anim.slide_in_up,
                PartialCustomTabDisplayManager.getStartAnimationOverride(null, provider, defId));

        // BOTTOM_SHEET -> slide up
        when(provider.getInitialActivityHeight()).thenReturn(1000);
        assertEquals(R.anim.slide_in_up,
                PartialCustomTabDisplayManager.getStartAnimationOverride(null, provider, defId));

        // SIDE_SHEET -> slide from side
        when(provider.getSideSheetPosition()).thenReturn(ACTIVITY_SIDE_SHEET_POSITION_END);
        when(provider.getInitialActivityWidth()).thenReturn(1000);
        when(provider.getActivityBreakPoint()).thenReturn(500);

        Activity act = mPCCTTestRule.mActivity;
        when(provider.getSideSheetSlideInBehavior())
                .thenReturn(ACTIVITY_SIDE_SHEET_SLIDE_IN_FROM_SIDE);
        assertEquals(R.anim.slide_in_right,
                PartialCustomTabDisplayManager.getStartAnimationOverride(act, provider, defId));

        LocalizationUtils.setRtlForTesting(true);
        assertEquals(R.anim.slide_in_left,
                PartialCustomTabDisplayManager.getStartAnimationOverride(act, provider, defId));

        // BOTTOM_SHEET -> slide up (with height and width set, compact portrait)
        mPCCTTestRule.configCompactDevice_Portrait();
        when(provider.getInitialActivityWidth()).thenReturn(500);
        when(provider.getActivityBreakPoint()).thenReturn(50);
        assertEquals(R.anim.slide_in_up,
                PartialCustomTabDisplayManager.getStartAnimationOverride(act, provider, defId));
    }
}
