/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wm;

import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.TYPE_EXTERNAL;
import static android.view.Display.TYPE_INTERNAL;
import static android.view.InsetsSource.FLAG_INSETS_ROUNDED_CORNER;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;

import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.RESTARTING_PROCESS;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
import static com.android.server.wm.ActivityRecord.State.STOPPED;
import static com.android.server.wm.AppCompatConfiguration.LETTERBOX_POSITION_MULTIPLIER_CENTER;
import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
import static com.android.server.wm.WindowContainer.POSITION_TOP;

import static com.google.common.truth.Truth.assertThat;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.WindowConfiguration;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Binder;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.InputDevice;
import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.WindowInsets;
import android.view.WindowManager;

import androidx.test.filters.FlakyTest;
import androidx.test.filters.MediumTest;

import com.android.internal.policy.SystemBarUtils;
import com.android.internal.statusbar.LetterboxDetails;
import com.android.server.statusbar.StatusBarManagerInternal;
import com.android.server.wm.DeviceStateController.DeviceStateEnum;
import com.android.window.flags.Flags;

import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.MockitoSession;

import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * Tests for Size Compatibility mode.
 *
 * Build/Install/Run:
 *  atest WmTests:SizeCompatTests
 */
@MediumTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class SizeCompatTests extends WindowTestsBase {
    private static final String CONFIG_NEVER_CONSTRAIN_DISPLAY_APIS =
            "never_constrain_display_apis";
    private static final String CONFIG_ALWAYS_CONSTRAIN_DISPLAY_APIS =
            "always_constrain_display_apis";
    private static final String CONFIG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES =
            "never_constrain_display_apis_all_packages";

    private static final float DELTA_ASPECT_RATIO_TOLERANCE = 0.005f;

    private static final double DELTA_COMPAT_SCALE_TOLERANCE = 1e7;

    @Rule
    public TestRule compatChangeRule = new PlatformCompatChangeRule();

    private Task mTask;
    private ActivityRecord mActivity;
    private ActivityMetricsLogger mActivityMetricsLogger;
    private Properties mInitialConstrainDisplayApisFlags;

    @Before
    public void setUp() throws Exception {
        mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
        clearInvocations(mActivityMetricsLogger);
        doReturn(mActivityMetricsLogger).when(mAtm.mTaskSupervisor).getActivityMetricsLogger();
        mInitialConstrainDisplayApisFlags = DeviceConfig.getProperties(
                NAMESPACE_CONSTRAIN_DISPLAY_APIS);
        // Provide empty default values for the configs.
        setNeverConstrainDisplayApisFlag("", true);
        setNeverConstrainDisplayApisAllPackagesFlag(false, true);
        setAlwaysConstrainDisplayApisFlag("", true);
    }

    @After
    public void tearDown() throws Exception {
        DeviceConfig.setProperties(mInitialConstrainDisplayApisFlags);
    }

    private ActivityRecord setUpApp(DisplayContent display) {
        return setUpApp(display, null /* appBuilder */, null /* taskBuilder */);
    }

    private ActivityRecord setUpApp(DisplayContent display, ActivityBuilder appBuilder) {
        return setUpApp(display, appBuilder, null /* taskBuilder */);
    }

    private ActivityRecord setUpApp(DisplayContent display, ActivityBuilder aBuilder,
            TaskBuilder tBuilder) {
        // Use the real package name (com.android.frameworks.wmtests) so that
        // EnableCompatChanges/DisableCompatChanges can take effect.
        // Otherwise the fake WindowTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME will make
        // PlatformCompat#isChangeEnabledByPackageName always return default value.
        final ComponentName componentName = ComponentName.createRelative(
                mContext, SizeCompatTests.class.getName());
        final TaskBuilder taskBuilder = tBuilder != null ? tBuilder : new TaskBuilder(mSupervisor);
        mTask = taskBuilder.setDisplay(display).setComponent(componentName)
                .build();
        final ActivityBuilder appBuilder = aBuilder != null ? aBuilder : new ActivityBuilder(mAtm);
        mActivity = appBuilder.setTask(mTask).setComponent(componentName).build();
        doReturn(false).when(mActivity).isImmersiveMode(any());
        return mActivity;
    }

    private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh) {
        return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */, null /* taskBuilder */);
    }

    private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, ActivityBuilder appBuilder) {
        return setUpDisplaySizeWithApp(dw, dh, appBuilder, null /* taskBuilder */);
    }

    private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, TaskBuilder taskBuilder) {
        return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */, taskBuilder);
    }

    private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, ActivityBuilder appBuilder,
            TaskBuilder taskBuilder) {
        final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mAtm, dw, dh);
        return setUpApp(builder.build(), appBuilder, taskBuilder);
    }

    private void setUpLargeScreenDisplayWithApp(int dw, int dh) {
        final DisplayContent display = mDisplayContent;
        final DisplayInfo displayInfo = display.getDisplayInfo();
        displayInfo.logicalWidth = dw;
        displayInfo.logicalHeight = dh;
        // Prevent legacy sdk from being affected by INSETS_DECOUPLED_CONFIGURATION_ENFORCED.
        display.mInitialDisplayCutout = displayInfo.displayCutout = DisplayCutout.NO_CUTOUT;
        // Smallest screen width=747dp according to 1400/(300/160).
        display.mBaseDisplayDensity = displayInfo.logicalDensityDpi =
                TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY;
        doNothing().when(display).updateDisplayInfo(any());
        resizeDisplay(display, displayInfo.logicalWidth, displayInfo.logicalHeight);
        assertTrue(display.isLargeScreen());
        if (com.android.window.flags.Flags.universalResizableByDefault()) {
            assertTrue("Large screen must ignore orientation request",
                    display.getIgnoreOrientationRequest());
        } else {
            display.setIgnoreOrientationRequest(true);
        }
        setUpApp(display, null /* appBuilder */);
        spyOn(display.getDisplayRotation());
    }

    private void setUpLandscapeLargeScreenDisplayWithApp() {
        setUpLargeScreenDisplayWithApp(/* dw */ 2800, /* dh */ 1400);
    }

    private void setUpPortraitLargeScreenDisplayWithApp() {
        setUpLargeScreenDisplayWithApp(/* dw */ 1400, /* dh */ 2800);
    }

    @Test
    public void testHorizontalReachabilityEnabledForTranslucentActivities() {
        testReachabilityEnabledForTranslucentActivity(/* dw */ 2500,  /* dh */1000,
                SCREEN_ORIENTATION_PORTRAIT, /* minAspectRatio */ 0f,
                /* horizontalReachability */ true);
    }

    @Test
    public void testHorizontalReachabilityEnabled_TranslucentPortraitActivities_portraitDisplay() {
        testReachabilityEnabledForTranslucentActivity(/* dw */ 1400,  /* dh */1600,
                SCREEN_ORIENTATION_PORTRAIT, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                /* horizontalReachability */ true);
    }

    @Test
    public void testVerticalReachabilityEnabledForTranslucentActivities() {
        testReachabilityEnabledForTranslucentActivity(/* dw */ 1000,  /* dh */2500,
                SCREEN_ORIENTATION_LANDSCAPE, /* minAspectRatio */ 0f,
                /* horizontalReachability */ false);
    }

    @Test
    public void testVerticalReachabilityEnabled_TranslucentLandscapeActivities_landscapeDisplay() {
        testReachabilityEnabledForTranslucentActivity(/* dw */ 1600,  /* dh */1400,
                SCREEN_ORIENTATION_LANDSCAPE, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                /* horizontalReachability */ false);
    }

    private void testReachabilityEnabledForTranslucentActivity(int displayWidth, int displayHeight,
            @ScreenOrientation int screenOrientation, float minAspectRatio,
            boolean horizontalReachability) {
        setUpDisplaySizeWithApp(displayWidth, displayHeight);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        final AppCompatConfiguration config = mWm.mAppCompatConfiguration;
        config.setTranslucentLetterboxingOverrideEnabled(true);
        config.setLetterboxVerticalPositionMultiplier(0.5f);
        config.setIsVerticalReachabilityEnabled(true);
        config.setLetterboxHorizontalPositionMultiplier(0.5f);
        config.setIsHorizontalReachabilityEnabled(true);

        // Opaque activity
        prepareMinAspectRatio(mActivity, minAspectRatio, screenOrientation);
        addWindowToActivity(mActivity);
        mActivity.mRootWindowContainer.performSurfacePlacement();

        // Translucent Activity
        final ActivityRecord translucentActivity = new ActivityBuilder(mAtm)
                .setActivityTheme(android.R.style.Theme_Translucent)
                .setLaunchedFromUid(mActivity.getUid())
                .setScreenOrientation(screenOrientation)
                .build();
        mTask.addChild(translucentActivity);

        spyOn(translucentActivity.mAppCompatController.getLetterboxPolicy());
        doReturn(true).when(translucentActivity.mAppCompatController
                .getLetterboxPolicy()).shouldShowLetterboxUi(any());

        addWindowToActivity(translucentActivity);
        translucentActivity.mRootWindowContainer.performSurfacePlacement();

        final Function<ActivityRecord, Rect> innerBoundsOf =
                (ActivityRecord a) -> {
                    final Rect bounds = new Rect();
                    a.getLetterboxInnerBounds(bounds);
                    return bounds;
                };
        final Runnable checkLetterboxPositions = () -> assertEquals(innerBoundsOf.apply(mActivity),
                innerBoundsOf.apply(translucentActivity));
        final Runnable checkIsTop = () -> assertThat(
                innerBoundsOf.apply(translucentActivity).top).isEqualTo(0);
        final Runnable checkIsBottom = () -> assertThat(
                innerBoundsOf.apply(translucentActivity).bottom).isEqualTo(displayHeight);
        final Runnable checkIsLeft = () -> assertThat(
                innerBoundsOf.apply(translucentActivity).left).isEqualTo(0);
        final Runnable checkIsRight = () -> assertThat(
                innerBoundsOf.apply(translucentActivity).right).isEqualTo(displayWidth);
        final Runnable checkIsHorizontallyCentered = () -> assertThat(
                innerBoundsOf.apply(translucentActivity).left > 0
                        && innerBoundsOf.apply(translucentActivity).right < displayWidth).isTrue();
        final Runnable checkIsVerticallyCentered = () -> assertThat(
                innerBoundsOf.apply(translucentActivity).top > 0
                        && innerBoundsOf.apply(translucentActivity).bottom < displayHeight)
                .isTrue();

        if (horizontalReachability) {
            final Consumer<Integer> doubleClick =
                    (Integer x) -> {
                        mActivity.mAppCompatController.getReachabilityPolicy()
                                .handleDoubleTap(x, displayHeight / 2);
                        mActivity.mRootWindowContainer.performSurfacePlacement();
                    };

            // Initial state
            checkIsHorizontallyCentered.run();

            // Double-click left
            doubleClick.accept(/* x */ 10);
            checkLetterboxPositions.run();
            checkIsLeft.run();

            // Double-click right
            doubleClick.accept(/* x */ displayWidth - 100);
            checkLetterboxPositions.run();
            checkIsHorizontallyCentered.run();

            // Double-click right
            doubleClick.accept(/* x */ displayWidth - 100);
            checkLetterboxPositions.run();
            checkIsRight.run();

            // Double-click left
            doubleClick.accept(/* x */ 10);
            checkLetterboxPositions.run();
            checkIsHorizontallyCentered.run();
        } else {
            final Consumer<Integer> doubleClick =
                    (Integer y) -> {
                        mActivity.mAppCompatController.getReachabilityPolicy()
                                .handleDoubleTap(displayWidth / 2, y);
                        mActivity.mRootWindowContainer.performSurfacePlacement();
                    };

            // Initial state
            checkIsVerticallyCentered.run();

            // Double-click top
            doubleClick.accept(/* y */ 10);
            checkLetterboxPositions.run();
            checkIsTop.run();

            // Double-click bottom
            doubleClick.accept(/* y */ displayHeight - 100);
            checkLetterboxPositions.run();
            checkIsVerticallyCentered.run();

            // Double-click bottom
            doubleClick.accept(/* y */ displayHeight - 100);
            checkLetterboxPositions.run();
            checkIsBottom.run();

            // Double-click top
            doubleClick.accept(/* y */ 10);
            checkLetterboxPositions.run();
            checkIsVerticallyCentered.run();
        }
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testRepositionLandscapeImmersiveAppWithDisplayCutout() {
        final int dw = 2100;
        final int dh = 2000;
        final int cutoutHeight = 150;
        final TestDisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
                .setCanRotate(false)
                .setNotch(cutoutHeight)
                .build();
        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
        mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);

        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));

        doReturn(true).when(activity).isImmersiveMode(any());
        addWindowToActivity(activity);
        activity.mRootWindowContainer.performSurfacePlacement();

        final Function<ActivityRecord, Rect> innerBoundsOf =
                (ActivityRecord a) -> {
                    final Rect bounds = new Rect();
                    a.getLetterboxInnerBounds(bounds);
                    return bounds;
                };

        final Consumer<Integer> doubleClick =
                (Integer y) -> {
                    activity.mAppCompatController.getReachabilityPolicy()
                            .handleDoubleTap(dw / 2, y);
                    activity.mRootWindowContainer.performSurfacePlacement();
                };

        final Rect bounds = activity.getBounds();
        assertTrue(bounds.top > cutoutHeight && bounds.bottom < dh);
        assertEquals(dw, bounds.width());

        // Double click bottom.
        doubleClick.accept(dh - 10);
        assertEquals(dh, innerBoundsOf.apply(activity).bottom);

        // Double click top.
        doubleClick.accept(10);
        doubleClick.accept(10);
        assertEquals(cutoutHeight, innerBoundsOf.apply(activity).top);
    }

    @Test
    public void testRestartProcessIfVisible() {
        setUpDisplaySizeWithApp(1000, 2500);
        doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity);
        mActivity.setVisibleRequested(true);
        mActivity.setSavedState(null /* savedState */);
        mActivity.setState(RESUMED, "testRestart");
        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);

        final Rect originalOverrideBounds = new Rect(mActivity.getBounds());
        resizeDisplay(mTask.mDisplayContent, 600, 1200);
        // The visible activity should recompute configuration according to the last parent bounds.
        mAtm.mActivityClientController.restartActivityProcessIfVisible(mActivity.token);

        assertEquals(RESTARTING_PROCESS, mActivity.getState());
        assertNotEquals(originalOverrideBounds, mActivity.getBounds());

        // Even if the state is changed (e.g. a floating activity on top is finished and make it
        // resume), the restart procedure should recover the state and continue to kill the process.
        mActivity.setState(RESUMED, "anyStateChange");
        doReturn(true).when(mSupervisor).hasScheduledRestartTimeouts(mActivity);
        mAtm.mActivityClientController.activityStopped(mActivity.token, null /* icicle */,
                null /* persistentState */, null /* handoffActivityData */,
                null /* description */);
        assertEquals(RESTARTING_PROCESS, mActivity.getState());
        verify(mSupervisor).removeRestartTimeouts(mActivity);
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() {
        final float aspectRatio = 1.2f;
        final ActivityBuilder activityBuilder = new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)
                .setMinAspectRatio(aspectRatio)
                .setMaxAspectRatio(aspectRatio)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE);
        final int notchHeight = 100;
        final ActivityRecord activity = setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800)
                .setNotch(notchHeight).build(), activityBuilder);
        final Rect displayBounds = activity.mDisplayContent.getWindowConfiguration().getBounds();
        final Rect appBounds = activity.getWindowConfiguration().getAppBounds();

        // The parent configuration doesn't change since the first resolved configuration, so the
        // activity should fit in the parent naturally (size=583x700, appBounds=[9, 100 - 592, 800],
        // horizontal offset = round((600 - 583) / 2) = 9)).
        assertFitted(activity);
        final int offsetX = (int) ((1f + displayBounds.width() - appBounds.width()) / 2);
        // The bounds must be horizontal centered.
        assertEquals(offsetX, appBounds.left);
        assertEquals(appBounds.height(), displayBounds.height() - notchHeight);
        // Ensure the app bounds keep the declared aspect ratio.
        assertEquals(appBounds.height(), appBounds.width() * aspectRatio, 0.5f /* delta */);
        // The decor height should be a part of the effective bounds.
        assertEquals(activity.getBounds().height(), appBounds.height() + notchHeight);
        // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio.
        assertActivityMaxBoundsSandboxed(activity);
        // Activity max bounds ignore notch, since an app can be shown past the notch (although app
        // is currently limited by the notch).
        assertThat(activity.getWindowConfiguration().getMaxBounds().height())
                .isEqualTo(displayBounds.height());

        activity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
        assertFitted(activity);

        // After the orientation of activity is changed, the display is rotated, the aspect
        // ratio should be the same (bounds=[0, 0 - 800, 583], appBounds=[100, 0 - 800, 583]).
        assertEquals(appBounds.width(), appBounds.height() * aspectRatio, 0.5f /* delta */);
        // Activity max bounds are sandboxed.
        assertActivityMaxBoundsSandboxed(activity);

        activity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
        assertFitted(activity);
        // Activity max bounds should be sandboxed; activity is letterboxed due to aspect ratio.
        assertActivityMaxBoundsSandboxed(activity);
        // Activity max bounds ignore notch, since an app can be shown past the notch (although app
        // is currently limited by the notch).
        assertThat(activity.getWindowConfiguration().getMaxBounds().height())
                .isEqualTo(displayBounds.height());
    }

    @Test
    public void testFixedScreenConfigurationWhenMovingToDisplay() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make a new less-tall display with lower density
        final DisplayContent newDisplay =
                new TestDisplayContent.Builder(mAtm, 1000, 2000)
                        .setDensityDpi(200).build();

        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);

        final Rect originalBounds = new Rect(mActivity.getBounds());
        final int originalDpi = mActivity.getConfiguration().densityDpi;

        // Move the non-resizable activity to the new display.
        mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);

        assertEquals(originalBounds.width(), mActivity.getBounds().width());
        assertEquals(originalBounds.height(), mActivity.getBounds().height());
        assertEquals(originalDpi, mActivity.getConfiguration().densityDpi);
        // Activity is sandboxed; it is in size compat mode since it is not resizable and has a
        // max aspect ratio.
        assertActivityMaxBoundsSandboxed();
        assertDownScaled();
    }

    @EnableFlags(Flags.FLAG_ENABLE_SIZE_COMPAT_MODE_IMPROVEMENTS_FOR_CONNECTED_DISPLAYS)
    @Test
    public void testFixedMiscConfigurationWhenMovingToDisplay() {
        setUpDisplaySizeWithApp(1000, 2500);

        final DisplayContent newDisplay =
                new TestDisplayContent.Builder(mAtm, 1000, 2000).build();
        final InputDevice device = new InputDevice.Builder()
                .setAssociatedDisplayId(newDisplay.mDisplayId)
                .setKeyboardType(InputDevice.KEYBOARD_TYPE_ALPHABETIC)
                .setSources(InputDevice.SOURCE_TOUCHSCREEN | InputDevice.SOURCE_TRACKBALL)
                .build();
        final InputDevice[] devices = {device};
        doReturn(true).when(newDisplay.mWmService.mInputManager)
                .canDispatchToDisplay(device.getId(), newDisplay.mDisplayId);
        doReturn(devices).when(newDisplay.mWmService.mInputManager).getInputDevices();
        mTask.mWmService.mIsTouchDevice = true;
        mTask.mWmService.displayReady();

        prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);

        final Configuration originalConfiguration = mActivity.getConfiguration();
        final int originalTouchscreen = originalConfiguration.touchscreen;
        final int originalNavigation = originalConfiguration.navigation;
        final int originalKeyboard = originalConfiguration.keyboard;

        // Move the non-resizable activity to the new display.
        mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);

        final Configuration newConfiguration = mActivity.getConfiguration();
        assertEquals(originalTouchscreen, newConfiguration.touchscreen);
        assertEquals(originalNavigation, newConfiguration.navigation);
        assertEquals(originalKeyboard, newConfiguration.keyboard);
        // TODO(b/399749909): assert keyboardHidden, hardkeyboardHidden, and navigationHidden too.
    }

    @Test
    public void testFixedScreenBoundsWhenDisplaySizeChanged() {
        setUpDisplaySizeWithApp(1000, 2500);
        prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
        final DisplayContent display = mActivity.mDisplayContent;
        assertFitted();
        // Activity inherits bounds from TaskDisplayArea, since not sandboxed.
        assertMaxBoundsInheritDisplayAreaBounds();

        final Rect origBounds = new Rect(mActivity.getBounds());
        final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();

        // Change the size of current display.
        resizeDisplay(display, 1000, 2000);
        // The bounds should be [100, 0 - 1100, 2500].
        assertEquals(origBounds.width(), currentBounds.width());
        assertEquals(origBounds.height(), currentBounds.height());
        assertDownScaled();

        // The scale is 2000/2500=0.8. The horizontal centered offset is (1000-(1000*0.8))/2=100.
        final float scale = (float) display.mBaseDisplayHeight / currentBounds.height();
        final int offsetX = (int) (display.mBaseDisplayWidth - (origBounds.width() * scale)) / 2;
        final int screenX = mActivity.getBounds().left;
        assertEquals(offsetX, screenX);

        // The position of configuration bounds should be in app space.
        assertEquals(screenX, (int) (currentBounds.left * scale + 0.5f));
        // Activity is sandboxed to the offset size compat bounds.
        assertActivityMaxBoundsSandboxed();

        // Change display size to a different orientation
        resizeDisplay(display, 2000, 1000);
        // The bounds should be [800, 0 - 1800, 2500].
        assertEquals(origBounds.width(), currentBounds.width());
        assertEquals(origBounds.height(), currentBounds.height());
        assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation);
        assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
        // Activity is sandboxed to the offset size compat bounds.
        assertActivityMaxBoundsSandboxed();

        // The previous resize operation doesn't consider the rotation change after size changed.
        // These setups apply the requested orientation to rotation as real case that the top fixed
        // portrait activity will determine the display rotation.
        final DisplayRotation displayRotation = display.getDisplayRotation();
        doCallRealMethod().when(displayRotation).updateRotationUnchecked(anyBoolean());
        // Skip unrelated layout procedures.
        mAtm.deferWindowLayout();
        display.reconfigureDisplayLocked();
        displayRotation.updateOrientation(display.getOrientation(), true /* forceUpdate */);
        display.sendNewConfiguration();

        assertEquals(Configuration.ORIENTATION_PORTRAIT, display.getConfiguration().orientation);
        assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
        // The size should still be in portrait [100, 0 - 1100, 2500] = 1000x2500.
        assertEquals(origBounds.width(), currentBounds.width());
        assertEquals(origBounds.height(), currentBounds.height());
        assertEquals(offsetX, mActivity.getBounds().left);
        assertDownScaled();
        // Activity is sandboxed due to size compat mode.
        assertActivityMaxBoundsSandboxed();

        final WindowState appWindow = addWindowToActivity(mActivity);
        assertTrue(mActivity.hasSizeCompatBounds());
        assertEquals("App window must use size compat bounds for layout in screen space",
                mActivity.getBounds(), appWindow.getBounds());
    }

    @Test
    public void testLetterboxDisplayedForWindowBelow() {
        setUpDisplaySizeWithApp(1000, 2500);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
        // Prepare two windows, one base app window below the splash screen
        final WindowState appWindow = addWindowToActivity(mActivity);
        final WindowState startWindow = addWindowToActivity(mActivity, TYPE_APPLICATION_STARTING);
        spyOn(appWindow);
        // Base app window is letterboxed for display cutout and splash screen is fullscreen
        doReturn(true).when(appWindow).isLetterboxedForDisplayCutout();

        mActivity.mRootWindowContainer.performSurfacePlacement();

        assertEquals(2, mActivity.mChildren.size());
        // Splash screen is still the activity's main window
        assertEquals(startWindow, mActivity.findMainWindow());
        assertFalse(startWindow.isLetterboxedForDisplayCutout());

        final Rect letterboxInnerBounds = new Rect();
        mActivity.getLetterboxInnerBounds(letterboxInnerBounds);
        // Letterboxed is still displayed for app window below splash screen
        assertFalse(letterboxInnerBounds.isEmpty());
    }

    @Test
    public void testLetterboxFullscreenBoundsAndNotImeAttachable() {
        final int displayWidth = 2500;
        setUpDisplaySizeWithApp(displayWidth, 1000);

        final float maxAspect = 1.5f;
        prepareUnresizable(mActivity, maxAspect, SCREEN_ORIENTATION_LANDSCAPE);
        assertFitted();

        final Rect bounds = mActivity.getBounds();
        assertEquals(bounds.width(), bounds.height() * maxAspect, 0.0001f /* delta */);
        // The position should be horizontal centered.
        assertEquals((displayWidth - bounds.width()) / 2, bounds.left);
        // Activity max bounds should be sandboxed since it is letterboxed.
        assertActivityMaxBoundsSandboxed();

        mActivity.mDisplayContent.setImeLayeringTarget(addWindowToActivity(mActivity));
        // Make sure IME cannot attach to the app, otherwise IME window will also be shifted.
        assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp());

        // Recompute the natural configuration without resolving size compat configuration.
        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // It should keep non-attachable because the resolved bounds will be computed according to
        // the aspect ratio that won't match its parent bounds.
        assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp());
        // Activity max bounds should be sandboxed since it is letterboxed.
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testIsLetterboxed_activityShowsWallpaper_returnsFalse() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
        final WindowState window = newWindowBuilder("window", TYPE_BASE_APPLICATION).setWindowToken(
                mActivity).build();

        assertEquals(window, mActivity.findMainWindow());

        doReturn(true).when(mActivity).isVisibleRequested();

        assertTrue(mActivity.mAppCompatController.getLetterboxPolicy()
                .shouldShowLetterboxUi(mActivity.findMainWindow()));

        window.mAttrs.flags |= FLAG_SHOW_WALLPAPER;

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .shouldShowLetterboxUi(mActivity.findMainWindow()));
    }

    @Test
    public void testIsLetterboxed_activityFromBubble_returnsFalse() {
        setUpPortraitLargeScreenDisplayWithApp();
        doReturn(true).when(mActivity).getLaunchedFromBubble();
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        assertFalse(mActivity.areBoundsLetterboxed());
    }

    @Test
    @EnableFlags(com.android.wm.shell.Flags.FLAG_ENABLE_CREATE_ANY_BUBBLE)
    // Enable to prevent any non-legacy override insets from being added.
    @EnableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testLegacyInsetOverride_isAppBubble_notApplied() {
        // Set up app.
        final int notchHeight = 100;
        final DisplayContent display =
                new TestDisplayContent.Builder(mAtm, 1000, 2800)
                        .setNotch(notchHeight)
                        .build();
        setUpApp(display);

        // Simulate inset override for legacy app bound behaviour.
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;

        // Set up activity task to be in app bubble.
        mTask.mLaunchNextToBubble = true;
        mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
        // App bounds should not include insets and should match bounds.
        assertEquals(new Rect(0, 0, 1000, 2800), appBounds);
        assertEquals(new Rect(0, 0, 1000, 2800), bounds);
    }

    @Test
    public void testAspectRatioMatchParentBoundsAndImeAttachable() {
        setUpApp(new TestDisplayContent.Builder(mAtm, 1000, 2000).build());
        prepareUnresizable(mActivity, 2f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED);
        assertFitted();

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
        final var appWindow = addWindowToActivity(mActivity);
        mActivity.mDisplayContent.setImeInputTarget(appWindow);
        mActivity.mDisplayContent.setImeLayeringTarget(appWindow);
        // Because the aspect ratio of display doesn't exceed the max aspect ratio of activity.
        // The activity should still fill its parent container and IME can attach to the activity.
        assertTrue(mActivity.matchParentBounds());
        assertTrue(mActivity.mDisplayContent.shouldImeAttachedToApp());

        final Rect letterboxInnerBounds = new Rect();
        mActivity.getLetterboxInnerBounds(letterboxInnerBounds);
        // The activity should not have letterbox.
        assertTrue(letterboxInnerBounds.isEmpty());
    }

    @Test
    public void testMoveToDifferentOrientationDisplay() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
        assertFitted();

        final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();
        final Rect currentAppBounds = mActivity.getWindowConfiguration().getAppBounds();
        final Rect originalBounds = new Rect(mActivity.getWindowConfiguration().getBounds());

        final int notchHeight = 100;
        final DisplayContent newDisplay = new TestDisplayContent.Builder(mAtm, 2000, 1000)
                .setCanRotate(false).setNotch(notchHeight).build();

        // Move the non-resizable activity to the new display.
        mTask.reparent(newDisplay.getDefaultTaskDisplayArea(), true /* onTop */);
        // The configuration bounds [820, 0 - 1820, 2500] should keep the same.
        assertEquals(originalBounds.width(), currentBounds.width());
        assertEquals(originalBounds.height(), currentBounds.height());
        assertDownScaled();
        // Activity max bounds are sandboxed due to size compat mode on the new display.
        assertActivityMaxBoundsSandboxed();

        final Rect newDisplayBounds = newDisplay.getWindowConfiguration().getBounds();
        // The scaled bounds should exclude notch area (1000 - 100 == 360 * 2500 / 1000 = 900).
        assertEquals(newDisplayBounds.height() - notchHeight,
                (int) ((float) mActivity.getBounds().width() * originalBounds.height()
                        / originalBounds.width()));

        // Recompute the natural configuration in the new display.
        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
        mActivity.ensureActivityConfiguration();
        // Because the display cannot rotate, the portrait activity will fit the short side of
        // display with keeping portrait bounds [200, 0 - 700, 1000] in center.
        assertEquals(newDisplayBounds.height(), currentBounds.height());
        assertEquals(currentAppBounds.height() * newDisplayBounds.height()
                / newDisplayBounds.width(), currentAppBounds.width());
        assertFitted();
        // The appBounds should be [200, 100 - 700, 1000].
        final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
        assertEquals(currentBounds.width(), appBounds.width());
        assertEquals(currentBounds.height() - notchHeight, appBounds.height());
        // Activity max bounds are sandboxed due to letterboxing from orientation mismatch with
        // display.
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testFixedOrientationRotateCutoutDisplay() {
        // Create a display with a notch/cutout
        final int notchHeight = 60;
        final int width = 1000;
        setUpApp(new TestDisplayContent.Builder(mAtm, width, 2500)
                .setNotch(notchHeight).build());
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        // Bounds=[0, 0 - 1000, 1400], AppBounds=[0, 60 - 1000, 1460].
        final float maxAspect = 1.4f;
        prepareUnresizable(mActivity, 1.4f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);

        final Rect currentBounds = mActivity.getWindowConfiguration().getBounds();
        final Rect appBounds = mActivity.getWindowConfiguration().getAppBounds();
        final Rect origBounds = new Rect(currentBounds);
        final Rect origAppBounds = new Rect(appBounds);

        // Activity is sandboxed, and bounds include the area consumed by the notch.
        assertActivityMaxBoundsSandboxed();
        assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds().height())
                .isEqualTo(Math.round(width * maxAspect) + notchHeight);

        // Although the activity is fixed orientation, force rotate the display.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_270);
        assertEquals(ROTATION_270, mTask.getWindowConfiguration().getRotation());

        assertEquals(origBounds.width(), currentBounds.width());
        // Make sure the app size is the same
        assertEquals(origAppBounds.width(), appBounds.width());
        assertEquals(origAppBounds.height(), appBounds.height());
        // The activity is 1000x1400 and the display is 2500x1000.
        assertDownScaled();
        final float scale = mActivity.getCompatScale();
        // The position in configuration should be in app coordinates.
        final Rect screenBounds = mActivity.getBounds();
        assertEquals(screenBounds.left, (int) (currentBounds.left * scale + 0.5f));
        assertEquals(screenBounds.top, (int) (currentBounds.top * scale + 0.5f));

        // Activity max bounds are sandboxed due to size compat mode.
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testFixedAspectRatioOrientationChangeOrientation() {
        setUpDisplaySizeWithApp(1000, 2500);

        final float maxAspect = 1.4f;
        prepareUnresizable(mActivity, maxAspect, SCREEN_ORIENTATION_PORTRAIT);
        // The display aspect ratio 2.5 > 1.4 (max of activity), so the size is fitted.
        assertFitted();

        final Rect originalBounds = new Rect(mActivity.getBounds());
        final Rect originalAppBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());

        assertEquals((int) (originalBounds.width() * maxAspect), originalBounds.height());
        // Activity is sandboxed due to fixed aspect ratio.
        assertActivityMaxBoundsSandboxed();

        // Prepare the states for verifying relaunching after changing orientation.
        mActivity.finishRelaunching();
        mActivity.setState(RESUMED, "testFixedAspectRatioOrientationChangeOrientation");
        mActivity.setLastReportedConfiguration(mAtm.getGlobalConfiguration(),
                mActivity.getConfiguration());

        // Change the fixed orientation.
        mActivity.setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
        assertTrue(mActivity.isRelaunching());
        assertTrue(mActivity.mAppCompatController.getOrientationOverrides()
                .getIsRelaunchingAfterRequestedOrientationChanged());

        assertFitted();
        assertEquals(originalBounds.width(), mActivity.getBounds().height());
        assertEquals(originalBounds.height(), mActivity.getBounds().width());
        assertEquals(originalAppBounds.width(),
                mActivity.getWindowConfiguration().getAppBounds().height());
        assertEquals(originalAppBounds.height(),
                mActivity.getWindowConfiguration().getAppBounds().width());
        // Activity is sandboxed due to fixed aspect ratio.
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testFixedScreenLayoutSizeBits() {
        setUpDisplaySizeWithApp(1000, 2500);
        final int fixedScreenLayout = Configuration.SCREENLAYOUT_LONG_NO
                | Configuration.SCREENLAYOUT_SIZE_NORMAL
                | Configuration.SCREENLAYOUT_COMPAT_NEEDED;
        final int layoutMask = Configuration.SCREENLAYOUT_LONG_MASK
                | Configuration.SCREENLAYOUT_SIZE_MASK
                | Configuration.SCREENLAYOUT_LAYOUTDIR_MASK
                | Configuration.SCREENLAYOUT_COMPAT_NEEDED;
        Configuration c = new Configuration(mTask.getRequestedOverrideConfiguration());
        c.screenLayout = fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR;
        mTask.onRequestedOverrideConfigurationChanged(c);
        prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_UNSPECIFIED);

        // The initial configuration should inherit from parent.
        assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_LTR,
                mActivity.getConfiguration().screenLayout & layoutMask);

        mTask.getConfiguration().screenLayout = Configuration.SCREENLAYOUT_LAYOUTDIR_RTL
                | Configuration.SCREENLAYOUT_LONG_YES | Configuration.SCREENLAYOUT_SIZE_LARGE;
        mActivity.onConfigurationChanged(mTask.getConfiguration());

        // The size and aspect ratio bits don't change, but the layout direction should be updated.
        assertEquals(fixedScreenLayout | Configuration.SCREENLAYOUT_LAYOUTDIR_RTL,
                mActivity.getConfiguration().screenLayout & layoutMask);
    }

    @Test
    public void testResetNonVisibleActivity() {
        setUpDisplaySizeWithApp(1000, 2500);
        prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_UNSPECIFIED);
        final DisplayContent display = mTask.mDisplayContent;
        // Resize the display so the activity is in size compatibility mode.
        resizeDisplay(display, 900, 1800);

        mActivity.setState(STOPPED, "testSizeCompatMode");
        mActivity.setVisibleRequested(false);
        mActivity.visibleIgnoringKeyguard = false;
        mActivity.app.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
        mActivity.app.computeProcessActivityState();

        // Simulate the display changes orientation.
        final Configuration rotatedConfig = rotateDisplay(display, ROTATION_90);
        // Size compatibility mode is able to handle orientation change so the process shouldn't be
        // restarted and the override configuration won't be cleared.
        verify(mActivity, never()).restartProcessIfVisible();
        assertDownScaled();
        // Activity max bounds are sandboxed due to size compat mode, even if is not visible.
        assertActivityMaxBoundsSandboxed();

        // Change display density
        display.mBaseDisplayDensity = (int) (0.7f * display.mBaseDisplayDensity);
        display.computeScreenConfiguration(rotatedConfig);
        mAtm.mAmInternal = mock(ActivityManagerInternal.class);
        display.onRequestedOverrideConfigurationChanged(rotatedConfig);

        // The override configuration should be reset and the activity's process will be killed.
        assertFitted();
        verify(mActivity).restartProcessIfVisible();
        waitHandlerIdle(mAtm.mH);
        verify(mAtm.mAmInternal).killProcess(
                eq(mActivity.app.mName), eq(mActivity.app.mUid), anyString());
    }

    /**
     * Ensures that {@link TaskOrganizerController} can receive callback about the activity in size
     * compatibility mode.
     */
    @Test
    public void testHandleActivitySizeCompatModeChanged() {
        setUpDisplaySizeWithApp(1000, 2000);
        doReturn(true).when(mTask).isOrganized();
        mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
        assertFitted();

        // Resize the display so that the activity exercises size-compat mode.
        resizeDisplay(mTask.mDisplayContent, 1000, 2500);

        // Expect the exact token when the activity is in size compatibility mode.
        verify(mTask).onSizeCompatActivityChanged();
        ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();

        assertTrue(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());

        // Make the activity resizable again by restarting it
        clearInvocations(mTask);
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
        mActivity.setVisibleRequested(true);
        mActivity.restartProcessIfVisible();
        // The full lifecycle isn't hooked up so manually set state to resumed
        mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
        mTask.mDisplayContent.handleActivitySizeCompatModeIfNeeded(mActivity);

        // Expect null token when switching to non-size-compat mode activity.
        verify(mTask).onSizeCompatActivityChanged();
        taskInfo = mTask.getTaskInfo();

        assertFalse(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());
    }

    @Test
    public void testHandleActivitySizeCompatModeChangedOnDifferentTask() {
        setUpDisplaySizeWithApp(1000, 2000);
        doReturn(true).when(mTask).isOrganized();
        mActivity.setState(RESUMED, "testHandleActivitySizeCompatModeChanged");
        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
        assertFitted();

        // Resize the display so that the activity exercises size-compat mode.
        resizeDisplay(mTask.mDisplayContent, 1000, 2500);

        // Expect the exact token when the activity is in size compatibility mode.
        verify(mTask).onSizeCompatActivityChanged();
        ActivityManager.RunningTaskInfo taskInfo = mTask.getTaskInfo();

        assertTrue(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());

        // Create another Task to hold another size compat activity.
        clearInvocations(mTask);
        final Task secondTask = new TaskBuilder(mSupervisor).setDisplay(mTask.getDisplayContent())
                .setCreateActivity(true).build();
        final ActivityRecord secondActivity = secondTask.getTopNonFinishingActivity();
        doReturn(true).when(secondTask).isOrganized();
        secondActivity.setState(RESUMED,
                "testHandleActivitySizeCompatModeChanged");
        prepareUnresizable(secondActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);

        // Resize the display so that the activity exercises size-compat mode.
        resizeDisplay(mTask.mDisplayContent, 1000, 3000);

        // Expect the exact token when the activity is in size compatibility mode.
        verify(secondTask).onSizeCompatActivityChanged();
        verify(mTask, never()).onSizeCompatActivityChanged();
        taskInfo = secondTask.getTaskInfo();

        assertTrue(taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat());
    }

    @Test
    public void testShouldCreateCompatDisplayInsetsOnResizeableTask() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = new ActivityBuilder(mAtm)
                .setTask(mTask)
                .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
                .build();
        assertTrue(activity.shouldCreateAppCompatDisplayInsets());

        // The non-resizable activity should not be size compat because it is on a resizable task
        // in multi-window mode.
        mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
        // Activity should not be sandboxed.
        assertMaxBoundsInheritDisplayAreaBounds();

        // The non-resizable activity should not be size compat because the display support
        // changing windowing mode from fullscreen to freeform.
        mTask.mDisplayContent.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
        // Activity should not be sandboxed.
        assertMaxBoundsInheritDisplayAreaBounds();
    }

    @Test
    public void testShouldCreateCompatDisplayInsetsWhenUnresizeableAndSupportsSizeChangesTrue() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    public void testShouldCreateCompatDisplayInsetsWhenUnresizeableAndSupportsSizeChangesFalse() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    public void testShouldCreateCompatDisplayInsetsWhenResizeableAndSupportsSizeChangesFalse() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    public void
            testShouldCreateCompatDisplayInsetsWhenUnfixedOrientationSupportsSizeChangesFalse() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
    public void testShouldCreateCompatDisplayInsetsWhenForceResizeAppOverrideSet() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
    public void testShouldCreateCompatDisplayInsetsWhenForceNonResizeOverrideSet() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
                RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
    public void testShouldCreateCompatDisplayInsetsWhenForceNonResizeSetAndUnfixedOrientation() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */true,
                RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    public void testShouldCreateCompatDisplayUserAspectRatioFullscreenOverride() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        // Simulate the user selecting the fullscreen user aspect ratio override
        spyOn(activity.mWmService.mAppCompatConfiguration);
        spyOn(activity.mAppCompatController.getAspectRatioOverrides());
        doReturn(true).when(activity.mWmService.mAppCompatConfiguration)
                .isUserAppAspectRatioFullscreenEnabled();
        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
                .when(activity.mAppCompatController.getAspectRatioOverrides())
                .getUserMinAspectRatioOverrideCode();
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER})
    public void testShouldNotCreateCompatDisplays_systemFullscreenOverride() {
        setUpDisplaySizeWithApp(1000, 2500);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);

        // Simulate the user selecting the fullscreen user aspect ratio override
        spyOn(activity.mAppCompatController.getAspectRatioOverrides());
        doReturn(true).when(
                activity.mAppCompatController.getAspectRatioOverrides())
                    .isSystemOverrideToFullscreenEnabled();
        assertFalse(activity.shouldCreateAppCompatDisplayInsets());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
    public void testNeverSandboxDisplayApis_configEnabled_sandboxingNotApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should not be sandboxed, even though it is letterboxed.
        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(activity.getDisplayArea().getBounds());
    }

    @Test
    @DisableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
    public void testNeverSandboxDisplayApis_configDisabled_sandboxingApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should be sandboxed due to letterboxed and the config being disabled.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    public void testNeverConstrainDisplayApisDeviceConfig_allPackagesFlagTrue_sandboxNotApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        setNeverConstrainDisplayApisAllPackagesFlag(true, false);
        // Setting 'never_constrain_display_apis' as well to make sure it is ignored.
        setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::", false);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should not be sandboxed, even though it is letterboxed.
        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(activity.getDisplayArea().getBounds());
    }

    @Test
    public void testNeverConstrainDisplayApisDeviceConfig_packageInRange_sandboxingNotApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        setNeverConstrainDisplayApisFlag(
                "com.android.frameworks.wmtests:20:,com.android.other::,"
                        + "com.android.frameworks.wmtests:0:10", false);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should not be sandboxed, even though it is letterboxed.
        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(activity.getDisplayArea().getBounds());
    }

    @Test
    public void testNeverConstrainDisplayApisDeviceConfig_packageOutsideRange_sandboxingApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        setNeverConstrainDisplayApisFlag("com.android.other::,com.android.frameworks.wmtests:1:5",
                false);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should be sandboxed due to letterboxed and the mismatch with flag.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    public void testNeverConstrainDisplayApisDeviceConfig_packageNotInFlag_sandboxingApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::", false);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should be sandboxed due to letterboxed and the mismatch with flag.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
    public void testAlwaysSandboxDisplayApis_configEnabled_sandboxingApplied_unresizable() {
        setUpDisplaySizeWithApp(1000, 1200);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should be sandboxed due to letterboxed and the config being enabled.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    @DisableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
    public void testAlwaysSandboxDisplayApis_configDisabled_sandboxingApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity max bounds should be sandboxed due to letterbox and the config being disabled.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
    public void testAlwaysSandboxDisplayApis_configEnabled_sandboxingApplied_resizableSplit() {
        setUpDisplaySizeWithApp(1000, 2800);
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_RESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, activity.getDisplayContent());

        // Activity max bounds should be sandboxed due the config being enabled.
        assertFalse(activity.inSizeCompatMode());
        assertActivityMaxBoundsSandboxed(activity);

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP,
                false /*moveParents*/, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());

        // Resizable activity is sandboxed due to config being enabled.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    public void testAlwaysConstrainDisplayApisDeviceConfig_packageInRange_sandboxingApplied() {
        setUpDisplaySizeWithApp(1000, 1200);

        setAlwaysConstrainDisplayApisFlag(
                "com.android.frameworks.wmtests:20:,com.android.other::,"
                        + "com.android.frameworks.wmtests:0:10", false);

        // Make the task root resizable.
        mActivity.info.resizeMode = RESIZE_MODE_RESIZEABLE;

        // Create an activity with a max aspect ratio on the same task.
        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */false,
                RESIZE_MODE_UNRESIZEABLE, ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(activity, /* maxAspect=*/ 1.5f, SCREEN_ORIENTATION_LANDSCAPE);

        // Resizable activity is sandboxed due to match with flag.
        assertActivityMaxBoundsSandboxed(activity);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
    public void testOverrideMinAspectRatioSmall_overridden() {
        final int dh = 1200;
        final int dw = 1000;
        final ActivityRecord activity = setUpDisplaySizeWithApp(dw, dh, new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        final Rect bounds = activity.getBounds();
        assertEquals(dh, bounds.height());
        assertEquals(dh / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE,
                bounds.width(), 0.5f);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
    public void testOverrideMinAspectRatioSmall_notOverridden() {
        final int dh = 1200;
        final int dw = 1000;
        final ActivityRecord activity = setUpDisplaySizeWithApp(dw, dh, new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));

        // Activity's requested aspect ratio is larger than OVERRIDE_MIN_ASPECT_RATIO_SMALL,
        // so OVERRIDE_MIN_ASPECT_RATIO_SMALL is ignored.
        final Rect bounds = activity.getBounds();
        assertEquals(dh, bounds.height());
        assertEquals(dh / OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                bounds.width(), 0.5f);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    public void testOverrideMinAspectRatioMedium() {
        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        // The per-package override forces the activity into a 3:2 aspect ratio
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testOverrideMinAspectRatioLowerThanManifest() {
        final int dh = 1800;
        final int notchHeight = 200;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, dh)
                .setNotch(notchHeight).setSystemDecorations(true).build();
        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).setMinAspectRatio(2f));

        // The per-package override should have no effect, because the manifest aspect ratio is
        // larger (2:1)
        final Rect appBounds = activity.getWindowConfiguration().getAppBounds();
        assertEquals("App bounds must have min aspect ratio", 2f,
                (float) appBounds.height() / appBounds.width(), 0.0001f /* delta */);
        assertEquals("Long side must fit task",
                dh - notchHeight, appBounds.height());
        assertEquals("Bounds can include insets", mTask.getBounds().height(),
                activity.getBounds().height());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    public void testOverrideMinAspectRatioLargerThanManifest() {
        final ActivityRecord activity = setUpDisplaySizeWithApp(1400, 1600,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                        .setMinAspectRatio(1.1f));

        // The per-package override should have no effect, because the manifest aspect ratio is
        // larger (2:1)
        assertEquals(1600, activity.getBounds().height());
        assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    public void testOverrideMinAspectRatioLarge() {
        final ActivityRecord activity = setUpDisplaySizeWithApp(1500, 1600,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        // The per-package override forces the activity into a 16:9 aspect ratio
        assertEquals(1600, activity.getBounds().height());
        assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    public void testOverrideMinAspectRatio_Both() {
        // If multiple override aspect ratios are set, we should use the largest one
        final ActivityRecord activity = setUpDisplaySizeWithApp(1400, 1600,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        // The per-package override forces the activity into a 16:9 aspect ratio
        assertEquals(1600, activity.getBounds().height());
        assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() {
        // In this test, the activity's orientation isn't fixed to portrait, therefore the override
        // isn't applied.
        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200);

        // The per-package override should have no effect
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1000, activity.getBounds().width());

        // After changing the orientation to portrait the override should be applied.
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();

        // The per-package override forces the activity into a 3:2 aspect ratio
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() {
        // In this test, the activity's orientation isn't fixed to portrait, therefore the override
        // isn't applied.
        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE));

        // The per-package override should have no effect
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1000, activity.getBounds().width());

        // After changing the orientation to portrait the override should be applied.
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();

        // The per-package override forces the activity into a 3:2 aspect ratio
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() {
        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        // The per-package override forces the activity into a 3:2 aspect ratio
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
                activity.getBounds().width(), 0.5);

        // After changing the orientation to landscape the override shouldn't be applied.
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();

        // The per-package override should have no effect
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1000, activity.getBounds().width());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
    public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() {
        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200);

        // The per-package override forces the activity into a 3:2 aspect ratio
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1200 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
    public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() {
        // In this test, the activity's orientation isn't fixed to portrait, therefore the override
        // isn't applied.
        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE));

        // The per-package override forces the activity into a 3:2 aspect ratio
        assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
                activity.getBounds().height(), 0.5);
        assertEquals(1000, activity.getBounds().width());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
    public void testOverrideMinAspectRatioWithoutGlobalOverride() {
        // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without
        // OVERRIDE_MIN_ASPECT_RATIO being also set.

        final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE));

        // The per-package override should have no effect
        assertEquals(1200, activity.getBounds().height());
        assertEquals(1000, activity.getBounds().width());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    public void testOverrideMinAspectRatioLargeForResizableAppInSplitScreen() {
        final ActivityRecord activity = setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800,
                new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, activity.getDisplayContent());

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());

        // The per-package override forces the activity into a 16:9 aspect ratio
        assertEquals(1400, activity.getBounds().height());
        assertEquals(1400 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                activity.getBounds().width(), 0.5);
    }


    /**
     * Test that a freeform unresizeable activity can be down-scaled to fill its smaller parent
     * bounds.
     */
    @Test
    public void testCompatScaling_freeformUnresizeableApp_largerThanParent_downScaled() {
        final int dw = 600;
        final int dh = 800;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
                .setWindowingMode(WINDOWING_MODE_FREEFORM)
                .build();
        setUpApp(display);
        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
        assertFalse(mActivity.inSizeCompatMode());

        // Resize app to make original app bounds larger than parent bounds.
        mTask.getWindowConfiguration().setAppBounds(
                new Rect(0, 0, dw - 300, dh - 400));
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // App should enter size compat mode and be down-scaled to fill new parent bounds.
        assertDownScaled();
    }

    /**
     * Test that when desktop mode is enabled, a freeform unresizeable activity can be up-scaled to
     * fill its larger parent bounds.
     */
    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
    public void testCompatScaling_freeformUnresizeableApp_smallerThanParent_upScaled() {
        doReturn(true).when(() ->
                DesktopModeHelper.canEnterDesktopMode(any()));
        final int dw = 600;
        final int dh = 800;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
                .setWindowingMode(WINDOWING_MODE_FREEFORM)
                .build();
        setUpApp(display);
        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
        assertFalse(mActivity.inSizeCompatMode());

        // Resize app to make original app bounds smaller than parent bounds.
        mTask.getWindowConfiguration().setAppBounds(
                new Rect(0, 0, dw + 300, dh + 400));
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // App should enter size compat mode and be up-scaled to fill parent bounds.
        assertUpScaled();
    }

    /**
     * Test that when desktop mode is disabled, a freeform unresizeable activity cannot be up-scaled
     * despite its larger parent bounds.
     */
    @Test
    @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
    public void testSizeCompatScaling_freeformUnresizeableApp_smallerThanParent_notScaled() {
        final int dw = 600;
        final int dh = 800;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
                .setWindowingMode(WINDOWING_MODE_FREEFORM)
                .build();
        setUpApp(display);
        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
        assertFalse(mActivity.inSizeCompatMode());
        final Rect originalAppBounds = mActivity.getBounds();

        // Resize app to make original app bounds smaller than parent bounds.
        mTask.getWindowConfiguration().setAppBounds(
                new Rect(0, 0, dw + 300, dh + 400));
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // App should enter size compat mode but remain its original size.
        assertTrue(mActivity.inSizeCompatMode());
        assertEquals(originalAppBounds, mActivity.getBounds());
    }

    /**
     * Test that when desktop mode is enabled, a freeform unresizeable activity is not up-scaled
     * when exiting freeform despite its larger parent bounds.
     */
    @Test
    @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MODE)
    public void testCompatScaling_freeformUnresizeableApp_exitFreeform_notScaled() {
        doReturn(true).when(() ->
                DesktopModeHelper.canEnterDesktopMode(any()));
        final int dw = 600;
        final int dh = 800;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
                .setWindowingMode(WINDOWING_MODE_FREEFORM)
                .build();
        setUpApp(display);
        prepareUnresizable(mActivity, /* maxAspect */ 0f, SCREEN_ORIENTATION_PORTRAIT);
        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
        final Rect originalAppBounds = mActivity.getBounds();

        assertFalse(mActivity.inSizeCompatMode());

        // Resize app to make original app bounds smaller than parent bounds.
        mTask.getWindowConfiguration().setAppBounds(
                new Rect(0, 0, dw + 300, dh + 400));
        // Change windowing mode from freeform to fullscreen
        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // App should enter size compat mode but remain its original size.
        assertTrue(mActivity.inSizeCompatMode());
        assertEquals(originalAppBounds, mActivity.getBounds());
    }

    @Test
    public void testGetLetterboxInnerBounds_noScalingApplied() {
        // Set up a display in portrait and ignoring orientation request.
        setUpPortraitLargeScreenDisplayWithApp();
        final int dw = mDisplayContent.mBaseDisplayWidth;
        final int dh = mDisplayContent.mBaseDisplayHeight;

        // Rotate display to landscape.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);

        // Need a window to call adjustBoundsForTaskbar with.
        addWindowToActivity(mActivity);

        // App should launch in fullscreen.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Activity inherits max bounds from TaskDisplayArea.
        assertMaxBoundsInheritDisplayAreaBounds();

        // Rotate display to portrait.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_0);

        final Rect rotatedDisplayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect rotatedActivityBounds = new Rect(mActivity.getBounds());
        assertTrue(rotatedDisplayBounds.width() < rotatedDisplayBounds.height());

        // App should be in size compat.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertDownScaled();
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        assertActivityMaxBoundsSandboxed();

        final int scale = dh / dw;

        // App bounds should be dh / scale x dw / scale
        assertEquals(dw, rotatedDisplayBounds.width());
        assertEquals(dh, rotatedDisplayBounds.height());

        assertEquals(dh / scale, rotatedActivityBounds.width());
        assertEquals(dw / scale, rotatedActivityBounds.height());

        // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
        mActivity.mRootWindowContainer.performSurfacePlacement();

        LetterboxDetails letterboxDetails = mActivity.mAppCompatController
                .getLetterboxPolicy().getLetterboxDetails();

        assertEquals(dh / scale, letterboxDetails.getLetterboxInnerBounds().width());
        assertEquals(dw / scale, letterboxDetails.getLetterboxInnerBounds().height());

        assertEquals(dw, letterboxDetails.getLetterboxFullBounds().width());
        assertEquals(dh, letterboxDetails.getLetterboxFullBounds().height());
    }

    @Test
    public void testLaunchWithFixedRotationTransform() {
        final int dw = 1000;
        final int dh = 2500;
        final int notchHeight = 200;
        setUpApp(new TestDisplayContent.Builder(mAtm, dw, dh).setNotch(notchHeight).build());
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        // The test assumes the notch will be at left side when the orientation is landscape.
        if (mContext.getResources().getBoolean(
                com.android.internal.R.bool.config_reverseDefaultRotation)) {
            setReverseDefaultRotation(mActivity.mDisplayContent, false);
        }
        addStatusBar(mActivity.mDisplayContent);

        mActivity.setVisibleRequested(false);
        requestTransition(mActivity, WindowManager.TRANSIT_OPEN);
        mActivity.setVisibility(true);
        final float maxAspect = 1.8f;
        prepareUnresizable(mActivity, maxAspect, SCREEN_ORIENTATION_LANDSCAPE);

        assertFitted();
        assertTrue(mActivity.isFixedRotationTransforming());
        // Display keeps in original orientation.
        assertEquals(Configuration.ORIENTATION_PORTRAIT,
                mActivity.mDisplayContent.getConfiguration().orientation);
        // The width should be restricted by the max aspect ratio = 1000 * 1.8 = 1800.
        assertEquals((int) (dw * maxAspect), mActivity.getBounds().width());
        // The notch is at the left side of the landscape activity. The bounds should be horizontal
        // centered in the remaining area [200, 0 - 2500, 1000], so its left should be
        // 200 + (2300 - 1800) / 2 = 450. The bounds should be [450, 0 - 2250, 1000].
        assertEquals(notchHeight + (dh - notchHeight - mActivity.getBounds().width()) / 2,
                mActivity.getBounds().left);

        // The letterbox needs a main window to layout.
        final WindowState w = addWindowToActivity(mActivity);
        // Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
        mActivity.mRootWindowContainer.performSurfacePlacement();
        // The letterbox insets should be [450, 0 - 250, 0].
        assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
                mActivity.getLetterboxInsets());

        final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy();
        // The activity doesn't fill the display, so the letterbox of the rotated activity is
        // overlapped with the rotated content frame of status bar. Hence the status bar shouldn't
        // be transparent.
        assertFalse(displayPolicy.isFullyTransparentAllowed(w, statusBars()));

        // Activity is sandboxed.
        assertActivityMaxBoundsSandboxed();

        // Make the activity fill the display.
        prepareUnresizable(mActivity, 10 /* maxAspect */, SCREEN_ORIENTATION_LANDSCAPE);
        w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
        // Refresh the letterbox.
        mActivity.mRootWindowContainer.performSurfacePlacement();

        // The letterbox should only cover the notch area, so status bar can be transparent.
        assertEquals(new Rect(notchHeight, 0, 0, 0), mActivity.getLetterboxInsets());
        assertTrue(displayPolicy.isFullyTransparentAllowed(w, statusBars()));
        assertActivityMaxBoundsSandboxed();

        // The insets state for metrics should be rotated (landscape).
        final InsetsState insetsState = new InsetsState();
        mActivity.mDisplayContent.getInsetsPolicy().getInsetsForWindowMetrics(
                mActivity, insetsState);
        assertEquals(dh, insetsState.getDisplayFrame().width());
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // Display shouldn't be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
                mActivity.mDisplayContent.getLastOrientation());
        assertTrue(displayBounds.width() > displayBounds.height());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertActivityMaxBoundsSandboxed();

        // Activity bounds should be 700x1400 with the ratio as the display.
        assertEquals(displayBounds.height(), activityBounds.height());
        assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
                activityBounds.width());
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMinAspectRatio() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
        // orientation letterbox.
        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
        mActivity.info.setMinAspectRatio(3);
        prepareUnresizable(mActivity, /* maxAspect= */ 0, SCREEN_ORIENTATION_PORTRAIT);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // Display shouldn't be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
                mActivity.mDisplayContent.getLastOrientation());
        assertTrue(displayBounds.width() > displayBounds.height());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Activity bounds should respect minimum aspect ratio for activity.
        assertEquals(displayBounds.height(), activityBounds.height());
        assertEquals((int) Math.rint(displayBounds.height()
                        / mActivity.info.getManifestMinAspectRatio()),
                activityBounds.width());
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_fixedOrientationAppRespectMaxAspectRatio() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app with max aspect ratio lower that aspect ratio override for fixed
        // orientation letterbox.
        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(3);
        prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // Display shouldn't be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
                mActivity.mDisplayContent.getLastOrientation());
        assertTrue(displayBounds.width() > displayBounds.height());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Activity bounds should respect maximum aspect ratio for activity.
        assertEquals(displayBounds.height(), activityBounds.height());
        assertEquals((int) Math.rint(displayBounds.height()
                        / mActivity.info.getMaxAspectRatio()),
                activityBounds.width());
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_fixedOrientationAppWithAspectRatioOverride() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        final float fixedOrientationLetterboxAspectRatio = 1.1f;
        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(
                fixedOrientationLetterboxAspectRatio);
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // Display shouldn't be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
                mActivity.mDisplayContent.getLastOrientation());
        assertTrue(displayBounds.width() > displayBounds.height());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Activity bounds should respect aspect ratio override for fixed orientation letterbox.
        assertEquals(displayBounds.height(), activityBounds.height());
        assertEquals((int) Math.rint(displayBounds.height() / fixedOrientationLetterboxAspectRatio),
                activityBounds.width());
    }

    @Test
    public void testDefaultLetterboxAspectRatioForMultiWindowMode_fixedOrientationApp() {
        // Set-up display in portrait.
        mAtm.mDevEnableNonResizableMultiWindow = true;
        final int screenWidth = 1100;
        final int screenHeight = 2100;
        setUpDisplaySizeWithApp(screenWidth, screenHeight);

        mActivity.mDisplayContent.getWindowConfiguration()
                .setAppBounds(/* left */ 0, /* top */ 0, screenWidth, screenHeight);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
        // Move activity to multi-window which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Unresizable portrait-only activity.
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // Activity should be letterboxed with an aspect ratio of 1.01.
        final Rect afterBounds = mActivity.getBounds();
        final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width();
        assertEquals(AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW,
                actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
        assertTrue(mActivity.areBoundsLetterboxed());
    }

    @Test
    public void
            testDefaultLetterboxAspectRatioForMultiWindowMode_fixedOrientationAppWithMinRatio() {
        // Set-up display in portrait.
        mAtm.mDevEnableNonResizableMultiWindow = true;
        final int screenWidth = 1100;
        final int screenHeight = 2100;
        setUpDisplaySizeWithApp(screenWidth, screenHeight);

        mActivity.mDisplayContent.getWindowConfiguration()
                .setAppBounds(/* left */ 0, /* top */ 0, screenWidth, screenHeight);

        // Set min aspect ratio to value greater than the default letterbox aspect ratio for
        // multi-window mode.
        final float minAspectRatio = 1.2f;
        mActivity.info.setMinAspectRatio(minAspectRatio);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
        // Move activity to multi-window which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Unresizable portrait-only activity.
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // Activity should be letterboxed with the min aspect ratio requested by the app NOT the
        // default letterbox aspect ratio for multi-window.
        final Rect afterBounds = mActivity.getBounds();
        final float actualAspectRatio = 1f * afterBounds.height() / afterBounds.width();
        assertEquals(minAspectRatio, actualAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
        assertTrue(mActivity.areBoundsLetterboxed());
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_unresizableWithCorrespondingMinAspectRatio() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        final float fixedOrientationLetterboxAspectRatio = 1.1f;
        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(
                fixedOrientationLetterboxAspectRatio);
        mActivity.mWmService.mAppCompatConfiguration.setDefaultMinAspectRatioForUnresizableApps(
                1.5f);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // Display shouldn't be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
                mActivity.mDisplayContent.getLastOrientation());
        assertTrue(displayBounds.width() > displayBounds.height());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Letterbox logic should use config_letterboxDefaultMinAspectRatioForUnresizableApps over
        // config_fixedOrientationLetterboxAspectRatio.
        assertEquals(displayBounds.height(), activityBounds.height());
        final float defaultAspectRatio = mActivity.mWmService.mAppCompatConfiguration
                .getDefaultMinAspectRatioForUnresizableApps();
        assertEquals(displayBounds.height() / defaultAspectRatio, activityBounds.width(), 0.5);
    }

    @Test
    public void testComputeConfigResourceOverrides_unresizableApp() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
        int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;

        // App should launch in fixed orientation letterbox.
        // Activity bounds should be 700x1400 with the ratio as the display.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFitted();
        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
        assertTrue(originalScreenWidthDp < originalScreenHeighthDp);

        // Rotate display to portrait.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // After we rotate, the activity should go in the size-compat mode and report the same
        // configuration values.
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
        assertEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);

        // Restart activity
        mActivity.restartProcessIfVisible();

        // Now configuration should be updated
        assertFitted();
        assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
        assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
        assertEquals(mActivity.getConfiguration().screenWidthDp,
                mActivity.getConfiguration().smallestScreenWidthDp);
    }

    @Test
    public void testComputeConfigResourceOverrides_resizableFixedOrientationActivity() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app without max aspect.
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);

        int originalScreenWidthDp = mActivity.getConfiguration().screenWidthDp;
        int originalScreenHeighthDp = mActivity.getConfiguration().screenHeightDp;

        // App should launch in fixed orientation letterbox.
        // Activity bounds should be 700x1400 with the ratio as the display.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFitted();
        assertEquals(originalScreenWidthDp, mActivity.getConfiguration().smallestScreenWidthDp);
        assertTrue(originalScreenWidthDp < originalScreenHeighthDp);

        // Rotate display to portrait.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // Now configuration should be updated
        assertFitted();
        assertNotEquals(originalScreenWidthDp, mActivity.getConfiguration().screenWidthDp);
        assertNotEquals(originalScreenHeighthDp, mActivity.getConfiguration().screenHeightDp);
        assertEquals(mActivity.getConfiguration().screenWidthDp,
                mActivity.getConfiguration().smallestScreenWidthDp);
    }

    @Test
    public void testSplitAspectRatioForUnresizablePortraitApps() {
        // Set up a display in landscape and ignoring orientation request.
        int screenWidth = 1600;
        int screenHeight = 1400;
        setUpDisplaySizeWithApp(screenWidth, screenHeight);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration
                .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);

        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        // Checking that there is no size compat mode.
        assertFitted();

        assertEquals(displayBounds.height(), activityBounds.height());
        assertTrue(activityBounds.width() < displayBounds.width() / 2);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
        // Checking that there is no size compat mode.
        assertFitted();
    }

    @Test
    public void testUserOverrideFullscreenForLandscapeDisplay() {
        final int displayWidth = 1600;
        final int displayHeight = 1400;
        setUpDisplaySizeWithApp(displayWidth, displayHeight);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        spyOn(mActivity.mWmService.mAppCompatConfiguration);
        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
                .isUserAppAspectRatioFullscreenEnabled();

        // Set user aspect ratio override
        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
                .when(mActivity.mAppCompatController.getAspectRatioOverrides())
                    .getUserMinAspectRatioOverrideCode();

        prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);

        final Rect bounds = mActivity.getBounds();

        // bounds should be fullscreen
        assertEquals(displayHeight, bounds.height());
        assertEquals(displayWidth, bounds.width());
    }

    @Test
    public void testUserOverrideFullscreenForPortraitDisplay() {
        final int displayWidth = 1400;
        final int displayHeight = 1600;
        setUpDisplaySizeWithApp(displayWidth, displayHeight);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        spyOn(mActivity.mWmService.mAppCompatConfiguration);
        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
                .isUserAppAspectRatioFullscreenEnabled();

        // Set user aspect ratio override
        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
        doReturn(USER_MIN_ASPECT_RATIO_FULLSCREEN)
                .when(mActivity.mAppCompatController.getAspectRatioOverrides())
                    .getUserMinAspectRatioOverrideCode();

        prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);

        final Rect bounds = mActivity.getBounds();

        // bounds should be fullscreen
        assertEquals(displayHeight, bounds.height());
        assertEquals(displayWidth, bounds.width());
    }

    @Test
    public void testSystemFullscreenOverrideForLandscapeDisplay() {
        setUpLandscapeLargeScreenDisplayWithApp();
        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
        final int displayHeight = mDisplayContent.mBaseDisplayHeight;

        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
        doReturn(true).when(
                mActivity.mAppCompatController.getAspectRatioOverrides())
                    .isSystemOverrideToFullscreenEnabled();

        prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);

        final Rect bounds = mActivity.getBounds();

        // bounds should be fullscreen
        assertEquals(displayHeight, bounds.height());
        assertEquals(displayWidth, bounds.width());
    }

    @Test
    public void testSystemFullscreenOverrideForPortraitDisplay() {
        setUpPortraitLargeScreenDisplayWithApp();
        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
        final int displayHeight = mDisplayContent.mBaseDisplayHeight;

        spyOn(mActivity.mAppCompatController.getAspectRatioOverrides());
        doReturn(true).when(
                mActivity.mAppCompatController.getAspectRatioOverrides())
                    .isSystemOverrideToFullscreenEnabled();

        prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_LANDSCAPE);

        final Rect bounds = mActivity.getBounds();

        // bounds should be fullscreen
        assertEquals(displayHeight, bounds.height());
        assertEquals(displayWidth, bounds.width());
    }

    @Test
    public void testUserOverrideSplitScreenAspectRatioForLandscapeDisplay() {
        final int displayWidth = 1600;
        final int displayHeight = 1400;
        setUpDisplaySizeWithApp(displayWidth, displayHeight);

        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);

        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
    }

    @Test
    public void testUserOverrideSplitScreenAspectRatioForPortraitDisplay() {
        final int displayWidth = 1400;
        final int displayHeight = 1600;
        setUpDisplaySizeWithApp(displayWidth, displayHeight);

        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);

        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
    }

    @Test
    public void testUserOverrideDisplaySizeAspectRatioForLandscapeDisplay() {
        final int displayWidth = 1600;
        final int displayHeight = 1400;
        setUpDisplaySizeWithApp(displayWidth, displayHeight);

        float expectedAspectRatio = 1f * displayWidth / displayHeight;

        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
    }

    @Test
    public void testUserOverrideDisplaySizeAspectRatioForPortraitDisplay() {
        final int displayWidth = 1400;
        final int displayHeight = 1600;
        setUpDisplaySizeWithApp(displayWidth, displayHeight);

        float expectedAspectRatio = 1f * displayHeight / displayWidth;

        testUserOverrideAspectRatio(expectedAspectRatio, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
    }

    @Test
    public void testUserOverride32AspectRatioForPortraitDisplay() {
        setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
        testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
    }

    @Test
    public void testUserOverride32AspectRatioForLandscapeDisplay() {
        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
        testUserOverrideAspectRatio(3 / 2f, USER_MIN_ASPECT_RATIO_3_2);
    }

    @Test
    public void testUserOverride43AspectRatioForPortraitDisplay() {
        setUpDisplaySizeWithApp(/* dw */ 1400, /* dh */ 1600);
        testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
    }

    @Test
    public void testUserOverride43AspectRatioForLandscapeDisplay() {
        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);
        testUserOverrideAspectRatio(4 / 3f, USER_MIN_ASPECT_RATIO_4_3);
    }

    @Test
    public void testUserOverride169AspectRatioForPortraitDisplay() {
        setUpDisplaySizeWithApp(/* dw */ 1800, /* dh */ 1500);
        testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
    }

    @Test
    public void testUserOverride169AspectRatioForLandscapeDisplay() {
        setUpDisplaySizeWithApp(/* dw */ 1500, /* dh */ 1800);
        testUserOverrideAspectRatio(16 / 9f, USER_MIN_ASPECT_RATIO_16_9);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    public void testUserOverrideAspectRatioOverSystemOverride() {
        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);

        testUserOverrideAspectRatio(false,
                SCREEN_ORIENTATION_PORTRAIT,
                3 / 2f,
                USER_MIN_ASPECT_RATIO_3_2,
                true);
    }

    @Test
    @FlakyTest(bugId = 299220009)
    public void testUserOverrideAspectRatioNotEnabled() {
        setUpDisplaySizeWithApp(/* dw */ 1600, /* dh */ 1400);

        // App aspect ratio doesn't change
        testUserOverrideAspectRatio(false,
                SCREEN_ORIENTATION_PORTRAIT,
                1f * 1600 / 1400,
                USER_MIN_ASPECT_RATIO_3_2,
                false);
    }

    private void testUserOverrideAspectRatio(float expectedAspectRatio,
            @PackageManager.UserMinAspectRatio int aspectRatio) {
        testUserOverrideAspectRatio(true,
                SCREEN_ORIENTATION_PORTRAIT,
                expectedAspectRatio,
                aspectRatio,
                true);

        testUserOverrideAspectRatio(false,
                SCREEN_ORIENTATION_PORTRAIT,
                expectedAspectRatio,
                aspectRatio,
                true);

        testUserOverrideAspectRatio(true,
                SCREEN_ORIENTATION_LANDSCAPE,
                expectedAspectRatio,
                aspectRatio,
                true);

        testUserOverrideAspectRatio(false,
                SCREEN_ORIENTATION_LANDSCAPE,
                expectedAspectRatio,
                aspectRatio,
                true);
    }

    private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
            float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
            boolean enabled) {
        final ActivityRecord activity = getActivityBuilderWithoutTask().build();
        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
                activity.mAppCompatController.getDesktopAspectRatioPolicy();
        spyOn(desktopAppCompatAspectRatioPolicy);
        doReturn(enabled).when(desktopAppCompatAspectRatioPolicy)
                .hasMinAspectRatioOverride(any());
        mTask.addChild(activity);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        spyOn(activity.mWmService.mAppCompatConfiguration);
        doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
                .isUserAppAspectRatioSettingsEnabled();
        final AppCompatAspectRatioOverrides aspectRatioOverrides =
                activity.mAppCompatController.getAspectRatioOverrides();
        spyOn(aspectRatioOverrides);
        // Set user aspect ratio override.
        doReturn(aspectRatio).when(aspectRatioOverrides).getUserMinAspectRatioOverrideCode();

        prepareLimitedBounds(activity, screenOrientation, isUnresizable);

        final Rect afterBounds = activity.getBounds();
        final int width = afterBounds.width();
        final int height = afterBounds.height();
        final float afterAspectRatio =
                (float) Math.max(width, height) / (float) Math.min(width, height);

        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
    public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() {
        final int displayWidth = 1400;
        final int displayHeight = 1600;
        final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
                new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
        // Setup Letterbox Configuration
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
        // Non-resizable portrait activity
        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
        final Rect afterBounds = activity.getBounds();
        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
    public void testOverrideSplitScreenAspectRatioForUnresizablePortraitAppsFromLandscape() {
        final int displayWidth = 1600;
        final int displayHeight = 1400;
        final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
                new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
        // Setup Letterbox Configuration
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
        // Non-resizable portrait activity
        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
        final Rect afterBounds = activity.getBounds();
        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
    public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeApps() {
        final int displayWidth = 1400;
        final int displayHeight = 1600;
        final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
                new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
        // Setup Letterbox Configuration
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
        // Non-resizable portrait activity
        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        float expectedAspectRatio = 1f * displayWidth / getExpectedSplitSize(displayHeight);
        final Rect afterBounds = activity.getBounds();
        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
    public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeAppsFromLandscape() {
        final int displayWidth = 1600;
        final int displayHeight = 1400;
        final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight,
                new ActivityBuilder(mAtm).setMinAspectRatio(1.1f));
        // Setup Letterbox Configuration
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f);
        // Non-resizable portrait activity
        prepareUnresizable(activity, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
        float expectedAspectRatio = 1f * displayHeight / getExpectedSplitSize(displayWidth);
        final Rect afterBounds = activity.getBounds();
        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
    public void testOverrideSplitScreenAspectRatio_splitScreenActivityInPortrait_notLetterboxed() {
        mAtm.mDevEnableNonResizableMultiWindow = true;
        final int screenWidth = 1800;
        final int screenHeight = 1000;
        final ActivityRecord activity = setUpDisplaySizeWithApp(screenWidth, screenHeight);

        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        // Simulate real display with top insets.
        final int topInset = 30;
        activity.mDisplayContent.getWindowConfiguration()
                .setAppBounds(0, topInset, screenWidth, screenHeight);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, activity.getDisplayContent());
        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());

        // Unresizable portrait-only activity.
        prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_PORTRAIT);

        // Activity should have the aspect ratio of a split screen activity and occupy exactly one
        // half of the screen, so there is no letterbox
        float expectedAspectRatio = 1f * screenHeight / getExpectedSplitSize(screenWidth);
        final Rect afterBounds = activity.getBounds();
        final float afterAspectRatio = (float) (afterBounds.height()) / afterBounds.width();
        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
        assertFalse(activity.areBoundsLetterboxed());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
    public void testOverrideSplitScreenAspectRatio_splitScreenActivityInLandscape_notLetterboxed() {
        mAtm.mDevEnableNonResizableMultiWindow = true;
        final int screenWidth = 1000;
        final int screenHeight = 1800;
        final ActivityRecord activity = setUpDisplaySizeWithApp(screenWidth, screenHeight);

        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        // Simulate real display with top insets.
        final int leftInset = 30;
        activity.mDisplayContent.getWindowConfiguration()
                .setAppBounds(leftInset, 0, screenWidth, screenHeight);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, activity.getDisplayContent());
        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());

        // Unresizable landscape-only activity.
        prepareUnresizable(activity, 3f, SCREEN_ORIENTATION_LANDSCAPE);

        // Activity should have the aspect ratio of a split screen activity and occupy exactly one
        // half of the screen, so there is no letterbox
        float expectedAspectRatio = 1f * screenWidth / getExpectedSplitSize(screenHeight);
        final Rect afterBounds = activity.getBounds();
        final float afterAspectRatio = (float) (afterBounds.width()) / afterBounds.height();
        assertEquals(expectedAspectRatio, afterAspectRatio, DELTA_ASPECT_RATIO_TOLERANCE);
        assertFalse(activity.areBoundsLetterboxed());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN})
    public void testOverrideMinAspectRatioExcludePortraitFullscreen() {
        resizeDisplay(mDisplayContent, 2600, 1600);
        mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);

        // Non-resizable portrait activity
        final ActivityRecord activity = setUpApp(mDisplayContent, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        // At first, OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply, because the
        // display is in landscape
        assertEquals(1600, activity.getBounds().height());
        assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                activity.getBounds().width(), 0.5);

        rotateDisplay(activity.mDisplayContent, ROTATION_90);
        prepareUnresizable(activity, /* maxAspect */ 0, SCREEN_ORIENTATION_PORTRAIT);

        // Now the display is in portrait fullscreen, so the override is applied making the content
        // fullscreen
        assertEquals(activity.getBounds(), activity.mDisplayContent.getBounds());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN})
    public void testOverrideMinAspectRatioExcludePortraitFullscreenNotApplied() {
        // In this test, the activity is not in fullscreen, so the override is not applied
        resizeDisplay(mDisplayContent, 2600, 1600);
        mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f);

        final ActivityRecord activity = setUpApp(mDisplayContent);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, activity.getDisplayContent());

        // Move first activity to split screen which takes half of the screen.
        organizer.mPrimary.setBounds(0, 0, 1300, 1600);
        organizer.putTaskToPrimary(mTask, true);

        // Non-resizable portrait activity
        prepareUnresizable(activity, /* maxAspect */ 0, SCREEN_ORIENTATION_PORTRAIT);

        // OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply here because the
        // display is not in fullscreen, so OVERRIDE_MIN_ASPECT_RATIO_LARGE applies instead
        assertEquals(1600, activity.getBounds().height());
        assertEquals(1600 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                activity.getBounds().width(), 0.5);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
    public void testOverrideRespectRequestedOrientationIsEnabled_orientationIsRespected() {
        // Set up a display in landscape
        setUpDisplaySizeWithApp(1000, 500);

        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        // Display should be rotated.
        assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());

        // No size compat mode
        assertFalse(activity.inSizeCompatMode());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_RESPECT_REQUESTED_ORIENTATION})
    public void testOverrideRespectRequestedOrientationIsEnabled_multiWindow_orientationIgnored() {
        // Set up a display in landscape
        setUpDisplaySizeWithApp(1000, 500);

        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
        TaskFragment taskFragment = activity.getTaskFragment();
        spyOn(taskFragment);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        doReturn(WINDOWING_MODE_MULTI_WINDOW).when(taskFragment).getWindowingMode();

        // Display should not be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());

        // No size compat mode
        assertFalse(activity.inSizeCompatMode());
    }

    @Test
    public void testSplitAspectRatioForUnresizableLandscapeApps() {
        // Set up a display in portrait and ignoring orientation request.
        setUpLargeScreenDisplayWithApp(1400, 2400);
        final int screenWidth = mDisplayContent.mBaseDisplayWidth;
        final int screenHeight = mDisplayContent.mBaseDisplayHeight;
        mActivity.mWmService.mAppCompatConfiguration
                .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);

        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect activityBounds = new Rect(mActivity.getBounds());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        // Checking that there is no size compat mode.
        assertFitted();

        assertEquals(displayBounds.width(), activityBounds.width());
        assertTrue(activityBounds.height() < displayBounds.height() / 2);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
        // Checking that there is no size compat mode.
        assertFitted();
    }

    @Test
    public void testDisplayAspectRatioForResizablePortraitApps() {
        // Set up a display in portrait and ignoring orientation request.
        setUpLargeScreenDisplayWithApp(1400, 2400);
        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
        mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f);

        // Enable display aspect ratio to take precedence before
        // fixedOrientationLetterboxAspectRatio
        mWm.mAppCompatConfiguration
                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);

        // Set up resizable app in portrait
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, false /* isUnresizable */);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, displayWidth, getExpectedSplitSize(displayHeight));
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        // Checking that there is no size compat mode.
        assertFitted();
        // Check that the display aspect ratio is used by the app.
        final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    public void testDisplayAspectRatioForResizableLandscapeApps() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();
        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
        final int displayHeight = mDisplayContent.mBaseDisplayHeight;
        mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(2f);

        // Enable display aspect ratio to take precedence before
        // fixedOrientationLetterboxAspectRatio
        mWm.mAppCompatConfiguration
                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);

        // Set up resizable app in landscape
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_LANDSCAPE, false /* isUnresizable */);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(displayWidth), displayHeight);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        // Checking that there is no size compat mode.
        assertFitted();
        // Check that the display aspect ratio is used by the app.
        final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    public void testDisplayAspectRatioForUnresizableLandscapeApps() {
        // Set up a display in portrait and ignoring orientation request.
        setUpPortraitLargeScreenDisplayWithApp();
        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
        final int displayHeight = mDisplayContent.mBaseDisplayHeight;

        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
        // Enable display aspect ratio to take precedence before
        // fixedOrientationLetterboxAspectRatio
        mWm.mAppCompatConfiguration
                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        // Checking that there is no size compat mode.
        assertFitted();
        // Check that the display aspect ratio is used by the app.
        final float targetMinAspectRatio = 1f * displayHeight / displayWidth;
        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    public void testDisplayAspectRatioForUnresizablePortraitApps() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();
        final int displayWidth = mDisplayContent.mBaseDisplayWidth;
        final int displayHeight = mDisplayContent.mBaseDisplayHeight;

        mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
        // Enable display aspect ratio to take precedence before
        // fixedOrientationLetterboxAspectRatio
        mWm.mAppCompatConfiguration
                .setIsDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // App should launch in fixed orientation letterbox.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        // Checking that there is no size compat mode.
        assertFitted();
        // Check that the display aspect ratio is used by the app.
        final float targetMinAspectRatio = 1f * displayWidth / displayHeight;
        assertEquals(targetMinAspectRatio, computeAspectRatio(mActivity.getBounds()),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    public void
            testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);

        final Rect activityBounds = new Rect(mActivity.getBounds());

        // Rotate display to portrait.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect newActivityBounds = new Rect(mActivity.getBounds());
        assertTrue(displayBounds.width() < displayBounds.height());

        // App should be in size compat.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        assertEquals(activityBounds.width(), newActivityBounds.width());
        assertEquals(activityBounds.height(), newActivityBounds.height());
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_sizeCompatAfterRotate() {
        // Set up a display in portrait and ignoring orientation request.
        setUpPortraitLargeScreenDisplayWithApp();

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);

        // App should launch in fullscreen.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        // Activity inherits max bounds from TaskDisplayArea.
        assertMaxBoundsInheritDisplayAreaBounds();

        // Rotate display to landscape.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        final Rect rotatedDisplayBounds = new Rect(mActivity.mDisplayContent.getBounds());
        final Rect rotatedActivityBounds = new Rect(mActivity.getBounds());
        assertTrue(rotatedDisplayBounds.width() > rotatedDisplayBounds.height());

        // App should be in size compat.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertDownScaled();
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        assertActivityMaxBoundsSandboxed();

        // App bounds should be 700x1400 with the ratio as the display.
        assertEquals(rotatedDisplayBounds.height(), rotatedActivityBounds.height());
        assertEquals(rotatedDisplayBounds.height() * rotatedDisplayBounds.height()
                / rotatedDisplayBounds.width(), rotatedActivityBounds.width());
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();
        final DisplayContent display = mActivity.mDisplayContent;

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Launch another portrait fixed app.
        spyOn(mTask);
        setBooted(display.mWmService.mAtmService);
        final ActivityRecord newActivity = getActivityBuilderWithoutTask()
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setTask(mTask)
                .build();

        // Update with new activity requested orientation and recompute bounds with no previous
        // size compat cache.
        verify(mTask).onDescendantOrientationChanged(same(newActivity));

        final Rect displayBounds = new Rect(display.getBounds());
        final Rect taskBounds = new Rect(mTask.getBounds());
        final Rect newActivityBounds = new Rect(newActivity.getBounds());

        // Task and display bounds should be equal while activity should be letterboxed and
        // has 700x1400 bounds with the ratio as the display.
        assertTrue(newActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(newActivity.inSizeCompatMode());
        // Activity max bounds are sandboxed due to size compat mode.
        assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(newActivity.getWindowConfiguration().getBounds());
        assertEquals(taskBounds, displayBounds);
        assertEquals(displayBounds.height(), newActivityBounds.height());
        assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(),
                newActivityBounds.width());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
    public void testDisplayIgnoreOrientationRequest_orientationChangedToUnspecified() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        mActivity.setRequestedOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
        // Activity is not in size compat mode because the orientation change request came from the
        // app itself
        assertFalse(mActivity.inSizeCompatMode());
        assertEquals(mActivity.getResolvedOverrideConfiguration().orientation,
                Configuration.ORIENTATION_UNDEFINED);
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_newLaunchedMaxAspectApp() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();
        final DisplayContent display = mActivity.mDisplayContent;

        // Portrait fixed app without max aspect.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Launch another portrait fixed app with max aspect ratio as 1.3.
        spyOn(mTask);
        setBooted(display.mWmService.mAtmService);
        final ActivityRecord newActivity = getActivityBuilderWithoutTask()
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setMaxAspectRatio(1.3f)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setTask(mTask)
                .build();

        // Update with new activity requested orientation and recompute bounds with no previous
        // size compat cache.
        verify(mTask).onDescendantOrientationChanged(same(newActivity));

        final Rect displayBounds = new Rect(display.getBounds());
        final Rect taskBounds = new Rect(mTask.getBounds());
        final Rect newActivityBounds = new Rect(newActivity.getBounds());

        // Task bounds should fill parent bounds.
        assertEquals(displayBounds, taskBounds);

        // Prior and new activity max bounds are sandboxed due to letterbox.
        assertThat(newActivity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(newActivityBounds);
        assertActivityMaxBoundsSandboxed();

        // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio.
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(newActivity.inSizeCompatMode());
        assertEquals(displayBounds.height(), newActivityBounds.height());
        assertEquals((long) Math.rint(newActivityBounds.height()
                        / newActivity.info.getMaxAspectRatio()),
                newActivityBounds.width());
    }

    @Test
    @SuppressWarnings("GuardedBy")
    public void testDisplayIgnoreOrientationRequest_pausedAppNotLostSizeCompat() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        // Portrait fixed app.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
        clearInvocations(mActivity);

        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());

        // Rotate display to portrait.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // App should be in size compat.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        // Activity max bounds are sandboxed due to size compat mode.
        assertActivityMaxBoundsSandboxed();

        final Rect activityBounds = new Rect(mActivity.getBounds());
        mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */,
                false /* deferPause */);

        // App still in size compat, and the bounds don't change.
        final AppCompatSizeCompatModePolicy scmPolicy = mActivity.mAppCompatController
                .getSizeCompatModePolicy();
        spyOn(scmPolicy);
        verify(scmPolicy, never()).clearSizeCompatMode();
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        assertEquals(activityBounds, mActivity.getBounds());
        // Activity max bounds are sandboxed due to size compat.
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_rotated180_notInSizeCompat() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();
        final DisplayContent display = mActivity.mDisplayContent;

        // Portrait fixed app.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);

        // In fixed orientation letterbox
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertActivityMaxBoundsSandboxed();

        // Rotate display to portrait.
        rotateDisplay(display, ROTATION_90);

        // App should be in size compat.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertThat(mActivity.inSizeCompatMode()).isTrue();
        assertActivityMaxBoundsSandboxed();

        // Rotate display to landscape.
        rotateDisplay(display, ROTATION_180);

        // In activity letterbox
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testDisplayIgnoreOrientationRequestWithInsets_rotated180_notInSizeCompat() {
        // Set up a display in portrait with display cutout and ignoring orientation request.
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, 2800)
                .setNotch(75)
                .build();
        setUpApp(display);
        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        // Landscape fixed app.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);

        // In fixed orientation letterbox
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertActivityMaxBoundsSandboxed();

        // Rotate display to landscape.
        rotateDisplay(display, ROTATION_90);

        // App should be in size compat.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertDownScaled();
        assertActivityMaxBoundsSandboxed();

        // Rotate display to portrait.
        rotateDisplay(display, ROTATION_180);

        // In fixed orientation letterbox
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testDisplayIgnoreOrientationRequest_disabledViaDeviceConfig_orientationRespected() {
        // Set up a display in landscape
        setUpDisplaySizeWithApp(2800, 1400);

        final ActivityRecord activity = buildActivityRecord(/* supportsSizeChanges= */ false,
                RESIZE_MODE_UNRESIZEABLE, SCREEN_ORIENTATION_PORTRAIT);
        activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        spyOn(activity.mWmService.mAppCompatConfiguration);
        doReturn(true).when(activity.mWmService.mAppCompatConfiguration)
                .isIgnoreOrientationRequestAllowed();

        // Display should not be rotated.
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, activity.mDisplayContent.getOrientation());

        doReturn(false).when(activity.mWmService.mAppCompatConfiguration)
                .isIgnoreOrientationRequestAllowed();

        // Display should be rotated.
        assertEquals(SCREEN_ORIENTATION_PORTRAIT, activity.mDisplayContent.getOrientation());
    }

    @Test
    public void testSandboxDisplayApis_unresizableAppNotSandboxed() {
        // Set up a display in landscape with an unresizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setSandboxDisplayApis(false /* sandboxDisplayApis */);
        prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
        assertFitted();

        // Activity max bounds not be sandboxed since sandboxing is disabled.
        assertMaxBoundsInheritDisplayAreaBounds();
    }

    @Test
    public void testSandboxDisplayApis_unresizableAppSandboxed() {
        // Set up a display in landscape with an unresizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setSandboxDisplayApis(true /* sandboxDisplayApis */);
        prepareUnresizable(mActivity, 1.5f, SCREEN_ORIENTATION_LANDSCAPE);
        assertFitted();

        // Activity max bounds should be sandboxed since sandboxing is enabled.
        assertActivityMaxBoundsSandboxed();
    }

    @Test
    public void testResizableApp_notSandboxed() {
        // Set up a display in landscape with a fully resizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        prepareLimitedBounds(mActivity, /* maxAspect= */ -1,
                SCREEN_ORIENTATION_UNSPECIFIED, /* isUnresizable= */ false);
        assertFitted();

        // Activity max bounds not be sandboxed since app is fully resizable.
        assertMaxBoundsInheritDisplayAreaBounds();
    }

    @Test
    public void testResizableMaxAspectApp_notSandboxed() {
        // Set up a display in landscape with a fully resizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        prepareLimitedBounds(mActivity, /* maxAspect= */ 1,
                SCREEN_ORIENTATION_UNSPECIFIED, /* isUnresizable= */ false);
        assertFitted();

        // Activity max bounds not be sandboxed since app is fully resizable.
        assertMaxBoundsInheritDisplayAreaBounds();
    }

    @Test
    public void testResizableOrientationRequestApp_notSandboxed() {
        // Set up a display in landscape with a fully resizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        prepareLimitedBounds(mActivity, /* maxAspect= */ -1,
                SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        assertFitted();

        // Activity max bounds not be sandboxed since app is fully resizable.
        assertMaxBoundsInheritDisplayAreaBounds();
    }

    @Test
    public void testResizableMaxAspectOrientationRequestApp_notSandboxed() {
        // Set up a display in landscape with a fully resizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        prepareLimitedBounds(mActivity, /* maxAspect= */ 1,
                SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        assertFitted();

        // Activity max bounds not be sandboxed since app is fully resizable.
        assertMaxBoundsInheritDisplayAreaBounds();
    }

    @Test
    public void testUnresizableApp_isSandboxed() {
        // Set up a display in landscape with a fully resizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        prepareLimitedBounds(mActivity, /* maxAspect= */ -1,
                SCREEN_ORIENTATION_UNSPECIFIED, /* isUnresizable= */ true);
        assertFitted();

        // Activity max bounds are sandboxed since app may enter size compat mode.
        assertActivityMaxBoundsSandboxed();
        assertFalse(mActivity.inSizeCompatMode());
    }

    @Test
    public void testUnresizableMaxAspectApp_isSandboxed() {
        // Set up a display in landscape with a fully resizable app.
        setUpDisplaySizeWithApp(2500, 1000);
        prepareLimitedBounds(mActivity, /* maxAspect= */ 1,
                SCREEN_ORIENTATION_UNSPECIFIED, /* isUnresizable= */ true);
        assertFitted();

        // Activity max bounds are sandboxed since app may enter size compat mode.
        assertActivityMaxBoundsSandboxed();
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.shouldCreateAppCompatDisplayInsets());

        // Resize display to half the width.
        resizeDisplay(mActivity.getDisplayContent(), 500, 1000);

        // Activity now in size compat mode.
        assertActivityMaxBoundsSandboxed();
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
    public void testTaskDisplayAreaNotFillDisplay() {
        setUpPortraitLargeScreenDisplayWithApp();
        final DisplayContent display = mActivity.mDisplayContent;
        display.setIgnoreOrientationRequest(false);
        final TaskDisplayArea taskDisplayArea = mActivity.getDisplayArea();
        taskDisplayArea.setBounds(0, 0, 1000, 2400);

        // Portrait fixed app.
        prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE);

        final Rect displayBounds = new Rect(display.getBounds());
        assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation);
        assertEquals(2800, displayBounds.width());
        assertEquals(1400, displayBounds.height());
        Rect displayAreaBounds = new Rect(0, 0, 2400, 1000);
        taskDisplayArea.setBounds(displayAreaBounds);

        final Rect activityBounds = new Rect(mActivity.getBounds());
        assertFalse(mActivity.inSizeCompatMode());
        assertEquals(2400, activityBounds.width());
        assertEquals(1000, activityBounds.height());
        // Task and activity maximum bounds inherit from TaskDisplayArea bounds.
        assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(displayAreaBounds);
        assertThat(mTask.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(displayAreaBounds);
    }

    @Test
    public void testSupportsNonResizableInSplitScreen_letterboxForDifferentOrientation() {
        // Support non resizable in multi window
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpDisplaySizeWithApp(1000, 2800);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Non-resizable landscape activity
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
        final Rect originalBounds = new Rect(mActivity.getBounds());

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP,
                false /*moveParents*/, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Non-resizable activity in size compat mode
        assertDownScaled();
        final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        assertEquals(originalBounds.width(), newBounds.width());
        assertEquals(originalBounds.height(), newBounds.height());
        assertActivityMaxBoundsSandboxed();

        recomputeNaturalConfigurationOfUnresizableActivity();

        // Split screen is also in portrait [1000,1400], so activity should be in fixed orientation
        // letterbox.
        assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
        assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
        assertFitted();
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertActivityMaxBoundsSandboxed();

        // Letterbox should fill the gap between the split screen and the letterboxed activity.
        assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds());
    }

    @Test
    public void testResizableFixedOrientationAppInSplitScreen_letterboxForDifferentOrientation() {
        setUpDisplaySizeWithApp(1000, 2800);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Resizable landscape-only activity.
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_LANDSCAPE, /* isUnresizable= */ false);

        final Rect originalBounds = new Rect(mActivity.getBounds());

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Resizable activity is not in size compat mode but in the letterbox for fixed orientation.
        assertFitted();
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
    }

    @Test
    public void testSupportsNonResizableInSplitScreen_aspectRatioLetterboxInSameOrientation() {
        // Support non resizable in multi window
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpDisplaySizeWithApp(1000, 2800);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Non-resizable portrait activity
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        final Rect originalBounds = new Rect(mActivity.getBounds());

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP,
                false /*moveParents*/, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Non-resizable activity in size compat mode
        assertDownScaled();
        final Rect newBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        assertEquals(originalBounds.width(), newBounds.width());
        assertEquals(originalBounds.height(), newBounds.height());
        assertActivityMaxBoundsSandboxed();

        recomputeNaturalConfigurationOfUnresizableActivity();

        // Split screen is also in portrait [1000,1400], which meets the activity request. It should
        // sandbox to the activity bounds for non-resizable.
        assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation);
        assertEquals(ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation);
        assertFitted();
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertActivityMaxBoundsSandboxed();

        // Activity bounds fill split screen.
        final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds());
        final Rect letterboxedBounds = new Rect(mActivity.getBounds());
        assertEquals(primarySplitBounds, letterboxedBounds);
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
    public void testSupportsNonResizableInSplitScreen_letterboxForAspectRatioRestriction() {
        // Support non resizable in multi window
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);

        // Bounds are letterboxed to respect the provided max aspect ratio.
        assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 1100));

        // Move activity to split screen which has landscape size.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents */ false, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 800);

        // Non-resizable activity should be in size compat mode.
        assertDownScaled();
        assertEquals(mActivity.getBounds(), new Rect(60, 0, 940, 800));

        recomputeNaturalConfigurationOfUnresizableActivity();

        // Activity should still be letterboxed but not in the size compat mode.
        assertFitted();
        assertEquals(mActivity.getBounds(), new Rect(60, 0, 940, 800));

        // Letterbox should fill the gap between the split screen and the letterboxed activity.
        assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(organizer.mPrimary.getBounds());
    }

    @Test
    public void testIsReachabilityEnabled_thisLetterbox_false() {
        // Case when the reachability would be enabled otherwise
        setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
        mActivity.getWindowConfiguration().setBounds(null);

        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ false);
        final AppCompatReachabilityOverrides reachabilityOverrides =
                mActivity.mAppCompatController.getReachabilityOverrides();
        assertFalse(reachabilityOverrides.isVerticalReachabilityEnabled());
        assertFalse(reachabilityOverrides.isHorizontalReachabilityEnabled());
    }

    @Test
    public void testIsHorizontalReachabilityEnabled_splitScreen_false() {
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpLandscapeLargeScreenDisplayWithApp();
        mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Unresizable portrait-only activity.
        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, 1400, 1000);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Horizontal reachability is disabled because the app is in split screen.
        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                .isHorizontalReachabilityEnabled());
    }

    @Test
    public void testIsVerticalReachabilityEnabled_splitScreen_false() {
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpDisplaySizeWithApp(1000, 2800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Unresizable landscape-only activity.
        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 1400);
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());

        // Vertical reachability is disabled because the app is in split screen.
        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                .isVerticalReachabilityEnabled());
    }

    @Test
    public void testIsVerticalReachabilityEnabled_doesNotMatchParentWidth_false() {
        setUpDisplaySizeWithApp(1000, 2800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        // Unresizable landscape-only activity.
        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_LANDSCAPE);

        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // Activity now in size compat mode.
        assertTrue(mActivity.inSizeCompatMode());

        // Vertical reachability is disabled because the app does not match parent width
        assertNotEquals(mActivity.getScreenResolvedBounds().width(),
                mActivity.mDisplayContent.getBounds().width());
        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                .isVerticalReachabilityEnabled());
    }

    @Test
    public void testIsVerticalReachabilityEnabled_emptyBounds_true() {
        setUpDisplaySizeWithApp(/* dw */ 1000, /* dh */ 2800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        // Set up activity with empty bounds to mock loading of app
        mActivity.getWindowConfiguration().setBounds(null);
        assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());

        // Vertical reachability is still enabled as resolved bounds is not empty
        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                .isVerticalReachabilityEnabled());
    }

    @Test
    public void testIsHorizontalReachabilityEnabled_emptyBounds_true() {
        setUpLandscapeLargeScreenDisplayWithApp();
        mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // Set up activity with empty bounds to mock loading of app
        mActivity.getWindowConfiguration().setBounds(null);
        assertEquals(new Rect(0, 0, 0, 0), mActivity.getBounds());

        // Horizontal reachability is still enabled as resolved bounds is not empty
        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                .isHorizontalReachabilityEnabled());
    }

    @Test
    public void testIsHorizontalReachabilityEnabled_portraitDisplayAndApp_true() {
        // Portrait display
        setUpDisplaySizeWithApp(1400, 1600);
        mActivity.mWmService.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        // 16:9f unresizable portrait app
        prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                .isHorizontalReachabilityEnabled());
    }

    @Test
    public void testIsVerticalReachabilityEnabled_landscapeDisplayAndApp_true() {
        // Landscape display
        setUpDisplaySizeWithApp(1600, 1500);
        mActivity.mWmService.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        // 16:9f unresizable landscape app
        prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
                SCREEN_ORIENTATION_LANDSCAPE);

        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                .isVerticalReachabilityEnabled());
    }

    @Test
    public void testIsHorizontalReachabilityEnabled_doesNotMatchParentHeight_false() {
        setUpLandscapeLargeScreenDisplayWithApp();
        mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        // Unresizable portrait-only activity.
        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_PORTRAIT);

        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // Activity now in size compat mode.
        assertTrue(mActivity.inSizeCompatMode());

        // Horizontal reachability is disabled because the app does not match parent height
        assertNotEquals(mActivity.getScreenResolvedBounds().height(),
                mActivity.mDisplayContent.getBounds().height());
        assertFalse(mActivity.mAppCompatController.getReachabilityOverrides()
                .isHorizontalReachabilityEnabled());
    }

    @Test
    public void testIsHorizontalReachabilityEnabled_inSizeCompatMode_matchesParentHeight_true() {
        setUpDisplaySizeWithApp(1800, 2200);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setIsHorizontalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        // Unresizable portrait-only activity.
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // Activity now in size compat mode.
        assertTrue(mActivity.inSizeCompatMode());

        // Horizontal reachability is enabled because the app matches parent height
        assertEquals(mActivity.getScreenResolvedBounds().height(),
                mActivity.mDisplayContent.getBounds().height());
        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                .isHorizontalReachabilityEnabled());
    }

    @Test
    public void testIsVerticalReachabilityEnabled_inSizeCompatMode_matchesParentWidth_true() {
        setUpDisplaySizeWithApp(2200, 1800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true);
        setUpAllowThinLetterboxed(/* thinLetterboxAllowed */ true);

        // Unresizable landscape-only activity.
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // Activity now in size compat mode.
        assertTrue(mActivity.inSizeCompatMode());

        // Vertical reachability is enabled because the app matches parent width
        assertEquals(mActivity.getScreenResolvedBounds().width(),
                mActivity.mDisplayContent.getBounds().width());
        assertTrue(mActivity.mAppCompatController.getReachabilityOverrides()
                .isVerticalReachabilityEnabled());
    }

    @Test
    public void testAppRequestsOrientationChange_notInSizeCompat() {
        setUpDisplaySizeWithApp(2200, 1800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        mActivity.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);

        // Activity is not in size compat mode because the orientation change request came from the
        // app itself
        assertFalse(mActivity.inSizeCompatMode());

        rotateDisplay(mActivity.mDisplayContent, ROTATION_270);
        // Activity should go into size compat mode now because the orientation change came from the
        // system (device rotation)
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
    public void testLetterboxDetailsForStatusBar_noLetterbox() {
        setUpDisplaySizeWithApp(2800, 1000);
        addStatusBar(mActivity.mDisplayContent);
        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
        // appearance inside letterboxDetails

        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
        StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
        // We should get a null LetterboxDetails object as there is no letterboxed activity, so
        // nothing will get passed to SysUI
        verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
                any(), anyBoolean(), anyInt(), anyInt(), isNull(), isNull());

    }

    @Test
    public void testLetterboxDetailsForStatusBar_letterboxedForMaxAspectRatio() {
        setUpDisplaySizeWithApp(2800, 1000);
        addStatusBar(mActivity.mDisplayContent);
        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
        // appearance inside letterboxDetails
        // Prepare unresizable activity with max aspect ratio
        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
        // Refresh the letterbox
        mActivity.mRootWindowContainer.performSurfacePlacement();

        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        assertEquals(mBounds, new Rect(850, 0, 1950, 1000));

        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
        LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
                mBounds,
                mActivity.getDisplayContent().getBounds(),
                mActivity.findMainWindow().mAttrs.insetsFlags.appearance
        )};

        // Check that letterboxDetails actually gets passed to SysUI
        StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
        verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
    }

    @Test
    public void testLetterboxDetailsForStatusBar_letterboxNotOverlappingStatusBar() {
        // Align to center so that we don't overlap with the status bar
        mAtm.mWindowManager.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
                .setNotch(100)
                .build();
        setUpApp(display);
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        TestWindowState statusBar = addStatusBar(mActivity.mDisplayContent);
        spyOn(statusBar);
        doReturn(new Rect(0, 0, statusBar.mRequestedWidth, statusBar.mRequestedHeight))
                .when(statusBar).getFrame();
        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
        // appearance inside letterboxDetails
        // Prepare unresizable activity with max aspect ratio
        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
        // Refresh the letterbox
        mActivity.mRootWindowContainer.performSurfacePlacement();

        Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        assertEquals(new Rect(0, 900, 1000, 2000), bounds);

        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
        LetterboxDetails[] expectedLetterboxDetails = {new LetterboxDetails(
                bounds,
                mActivity.getDisplayContent().getBounds(),
                mActivity.findMainWindow().mAttrs.insetsFlags.appearance
        )};

        // Check that letterboxDetails actually gets passed to SysUI
        StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
        verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
    }

    @Test
    public void testLetterboxDetailsForTaskBar_letterboxNotOverlappingTaskBar() {
        mAtm.mDevEnableNonResizableMultiWindow = true;
        final int screenHeight = 2200;
        final int screenWidth = 1400;
        final int taskbarHeight = 200;
        setUpDisplaySizeWithApp(screenWidth, screenHeight);

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Move first activity to split screen which takes half of the screen.
        organizer.mPrimary.setBounds(0, screenHeight / 2, screenWidth, screenHeight);
        organizer.putTaskToPrimary(mTask, true);

        final InsetsSource navSource = new InsetsSource(
                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
        navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));

        mActivity.mWmService.mAppCompatConfiguration.setLetterboxActivityCornersRadius(15);

        final WindowState w1 = addWindowToActivity(mActivity);
        w1.mAboveInsetsState.addSource(navSource);

        // Prepare unresizable activity with max aspect ratio
        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);

        // Refresh the letterboxes
        mActivity.mRootWindowContainer.performSurfacePlacement();

        final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
        verify(mTransaction, times(2)).setCrop(
                eq(w1.getSurfaceControl()),
                cropCapturer.capture()
        );
        final List<Rect> capturedCrops = cropCapturer.getAllValues();

        final int expectedHeight = screenHeight / 2 - taskbarHeight;
        assertEquals(2, capturedCrops.size());
        assertEquals(expectedHeight, capturedCrops.get(0).bottom);
        assertEquals(expectedHeight, capturedCrops.get(1).bottom);
    }

    @Test
    public void testLetterboxAlignedToBottom_NotOverlappingNavbar() {
        assertLandscapeActivityAlignedToBottomWithNavbar(false /* immersive */);
    }

    @Test
    public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() {
        assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */);
    }

    private void assertLandscapeActivityAlignedToBottomWithNavbar(boolean immersive) {
        setUpPortraitLargeScreenDisplayWithApp();
        final int screenHeight = mDisplayContent.mBaseDisplayHeight;
        final int screenWidth = mDisplayContent.mBaseDisplayWidth;
        final int taskbarHeight = 200;
        mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(1.0f);

        final InsetsSource navSource = new InsetsSource(
                InsetsSource.createId(null, 0, navigationBars()), navigationBars());
        navSource.setFlags(FLAG_INSETS_ROUNDED_CORNER, FLAG_INSETS_ROUNDED_CORNER);
        // Immersive activity has transient navbar
        navSource.setVisible(!immersive);
        navSource.setFrame(new Rect(0, screenHeight - taskbarHeight, screenWidth, screenHeight));
        mActivity.mWmService.mAppCompatConfiguration.setLetterboxActivityCornersRadius(15);

        final WindowState w1 = addWindowToActivity(mActivity);
        w1.mAboveInsetsState.addSource(navSource);

        // Prepare unresizable landscape activity
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
        doReturn(immersive).when(mActivity).isImmersiveMode(any());

        mActivity.mRootWindowContainer.performSurfacePlacement();

        LetterboxDetails letterboxDetails = mActivity.mAppCompatController
                .getLetterboxPolicy().getLetterboxDetails();

        // Letterboxed activity at bottom
        assertEquals(new Rect(0, 2100, 1400, 2800), mActivity.getBounds());
        final int expectedHeight = immersive ? screenHeight : screenHeight - taskbarHeight;
        assertEquals(expectedHeight, letterboxDetails.getLetterboxInnerBounds().bottom);
    }

    @Test
    public void testSplitScreenLetterboxDetailsForStatusBar_twoLetterboxedApps() {
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpDisplaySizeWithApp(2800, 1000);
        addStatusBar(mActivity.mDisplayContent);
        // Create another task for the second activity
        final Task newTask = new TaskBuilder(mSupervisor).setDisplay(mActivity.getDisplayContent())
                .setCreateActivity(true).build();
        ActivityRecord newActivity = newTask.getTopNonFinishingActivity();

        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Move first activity to split screen which takes half of the screen.
        organizer.mPrimary.setBounds(0, 0, 1400, 1000);
        organizer.putTaskToPrimary(mTask, true);
        // Move second activity to split screen which takes half of the screen.
        organizer.mSecondary.setBounds(1400, 0, 2800, 1000);
        organizer.putTaskToSecondary(newTask, true);

        addWindowToActivity(mActivity); // Add a window to the activity so that we can get an
        // appearance inside letterboxDetails
        // Prepare unresizable activity with max aspect ratio
        prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
        addWindowToActivity(newActivity);
        prepareUnresizable(newActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);

        // Refresh the letterboxes
        newActivity.mRootWindowContainer.performSurfacePlacement();

        Rect mBounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        assertEquals(mBounds, new Rect(150, 0, 1250, 1000));
        final Rect newBounds = new Rect(newActivity.getWindowConfiguration().getBounds());
        assertEquals(newBounds, new Rect(1550, 0, 2650, 1000));

        DisplayPolicy displayPolicy = mActivity.getDisplayContent().getDisplayPolicy();
        LetterboxDetails[] expectedLetterboxDetails = { new LetterboxDetails(
                mBounds,
                organizer.mPrimary.getBounds(),
                mActivity.findMainWindow().mAttrs.insetsFlags.appearance
        ), new LetterboxDetails(
                newBounds,
                organizer.mSecondary.getBounds(),
                newActivity.findMainWindow().mAttrs.insetsFlags.appearance
        )};

        // Check that letterboxDetails actually gets passed to SysUI
        StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
        verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
    }

    private void recomputeNaturalConfigurationOfUnresizableActivity() {
        // Recompute the natural configuration of the non-resizable activity and the split screen.
        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();

        // Draw letterbox.
        mActivity.ensureActivityConfiguration();
        addWindowToActivity(mActivity);
        mActivity.mRootWindowContainer.performSurfacePlacement();

        // Verify mAppCompatDisplayInsets is created after recomputing.
        assertTrue(mActivity.providesMaxBounds());
    }

    private void assertLetterboxSurfacesDrawnBetweenActivityAndParentBounds(Rect parentBounds) {
        // Letterbox should fill the gap between the parent bounds and the letterboxed activity.
        final Rect letterboxedBounds = new Rect(mActivity.getBounds());
        assertTrue(parentBounds.contains(letterboxedBounds));
        assertEquals(new Rect(letterboxedBounds.left - parentBounds.left,
                letterboxedBounds.top - parentBounds.top,
                parentBounds.right - letterboxedBounds.right,
                parentBounds.bottom - letterboxedBounds.bottom),
                mActivity.getLetterboxInsets());
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_leftInsets_appCentered() {
        // Set up folded display
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1100, 2100)
                .setCanRotate(true)
                .build();
        display.setIgnoreOrientationRequest(true);
        final DisplayPolicy policy = display.getDisplayPolicy();
        DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
        decorInfo.mNonDecorInsets.set(130, 0, 60, 0);
        spyOn(policy);
        doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
        mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);

        setUpApp(display);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // Resize the display to simulate unfolding in portrait
        resizeDisplay(mTask.mDisplayContent, 2200, 1800);
        assertTrue(mActivity.inSizeCompatMode());

        // Simulate real display not taking non-decor insets into consideration
        display.getWindowConfiguration().setAppBounds(0, 0, 2200, 1800);

        // Rotate display to landscape
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // App is centered
        assertEquals(mActivity.getBounds(), new Rect(350, 50, 1450, 2150));
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_left() {
        // Display configured as (2800, 1400).
        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
                /* letterboxHorizontalPositionMultiplier */ 0.0f,
                // At launch.
                /* fixedOrientationLetterbox */ new Rect(0, 0, 700, 1400),
                // After 90 degree rotation.
                /* sizeCompatUnscaled */ new Rect(0, 0, 700, 1400),
                // After the display is resized to (700, 1400).
                /* sizeCompatScaled */ new Rect(0, 0, 350, 700));
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_center() {
        // Display configured as (2800, 1400).
        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
                /* letterboxHorizontalPositionMultiplier */ 0.5f,
                // At launch.
                /* fixedOrientationLetterbox */ new Rect(1050, 0, 1750, 1400),
                // After 90 degree rotation.
                /* sizeCompatUnscaled */ new Rect(350, 0, 1050, 1400),
                // After the display is resized to (700, 1400).
                /* sizeCompatScaled */ new Rect(525, 0, 875, 700));
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_right() {
        // Display configured as (2800, 1400).
        assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
                /* letterboxHorizontalPositionMultiplier */ 1.0f,
                // At launch.
                /* fixedOrientationLetterbox */ new Rect(2100, 0, 2800, 1400),
                // After 90 degree rotation.
                /* sizeCompatUnscaled */ new Rect(700, 0, 1400, 1400),
                // After the display is resized to (700, 1400).
                /* sizeCompatScaled */ new Rect(1050, 0, 1400, 700));
    }

    private void assertHorizontalPositionForDifferentDisplayConfigsForPortraitActivity(
            float letterboxHorizontalPositionMultiplier, Rect fixedOrientationLetterbox,
            Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();

        mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
                letterboxHorizontalPositionMultiplier);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        assertEquals(fixedOrientationLetterbox, mActivity.getBounds());
        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
        assertTrue(mActivity.inSizeCompatMode());
        // Activity is in size compat mode but not scaled.
        assertEquals(sizeCompatUnscaled, mActivity.getBounds());
        // Force activity to scaled down for size compat mode.
        resizeDisplay(mTask.mDisplayContent, 700, 1400);
        assertTrue(mActivity.inSizeCompatMode());
        assertDownScaled();
        assertEquals(sizeCompatScaled, mActivity.getBounds());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION})
    public void testPortraitCloseToSquareDisplayWithTaskbar_insetsOverridden_notLetterboxed() {
        final DisplayContent display = mDisplayContent;
        // Set up portrait close to square display.
        resizeDisplay(display, 2200, 2280);
        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        // Simulate insets, final app bounds are (0, 0, 2200, 2130) - landscape.
        final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
                mDisplayContent).build();
        final Binder owner = new Binder();
        navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
                        .setInsetsSize(Insets.of(0, 0, 0, 150))
        };
        display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
        if (display.getDisplayPolicy().updateDecorInsetsInfo()) {
            display.sendNewConfiguration();
        }

        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        // Activity should not be letterboxed and should have portrait app bounds even though
        // orientation is not respected with insets as insets have been decoupled.
        final Rect appBounds = activity.getWindowConfiguration().getAppBounds();
        final Rect displayBounds = display.getBounds();
        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertNotNull(appBounds);
        assertEquals(displayBounds.width(), appBounds.width());
        assertEquals(displayBounds.height(), appBounds.height());
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() {
        final DisplayContent display = mDisplayContent;
        // Set up portrait close to square display
        resizeDisplay(display, 2200, 2280);
        display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
        final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
                mDisplayContent).build();
        final Binder owner = new Binder();
        navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
                        .setInsetsSize(Insets.of(0, 0, 0, 150))
        };
        display.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
        if (display.getDisplayPolicy().updateDecorInsetsInfo()) {
            display.sendNewConfiguration();
        }

        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        final Rect bounds = activity.getBounds();
        // Activity should be letterboxed and should have portrait app bounds
        assertTrue(activity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertTrue(bounds.height() > bounds.width());
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() {
        final DisplayContent dc = mDisplayContent;
        resizeDisplay(dc, 2200, 2280);
        dc.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape
        final WindowState navbar = newWindowBuilder("navbar", TYPE_NAVIGATION_BAR).setDisplay(
                mDisplayContent).build();
        final Binder owner = new Binder();
        navbar.mAttrs.providedInsets = new InsetsFrameProvider[] {
                new InsetsFrameProvider(owner, 0, WindowInsets.Type.navigationBars())
                        .setInsetsSize(Insets.of(0, 0, 0, 150))
        };
        dc.getDisplayPolicy().addWindowLw(navbar, navbar.mAttrs);
        if (dc.getDisplayPolicy().updateDecorInsetsInfo()) {
            dc.sendNewConfiguration();
        }

        final ActivityRecord activity = setUpApp(dc, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));
        // To force config to update again but with the same landscape orientation.
        activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);

        assertTrue(activity.shouldCreateAppCompatDisplayInsets());
        assertNotNull(activity.getAppCompatDisplayInsets());
        // Activity is not letterboxed for fixed orientation because orientation is respected
        // with insets, and should not be in size compat mode
        assertFalse(activity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(activity.inSizeCompatMode());
    }

    @Test
    public void testApplyAspectRatio_activityAlignWithParentAppVertical() {
        // The display's app bounds will be (0, 100, 1000, 2350)
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500)
                .setCanRotate(false)
                .setCutout(0, 100, 0, 150)
                .build();

        setUpApp(display);
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        prepareUnresizable(mActivity, 2.1f, SCREEN_ORIENTATION_UNSPECIFIED);
        // The activity height is 2100 and the display's app bounds height is 2250, so the activity
        // can be aligned inside parentAppBounds
        assertEquals(new Rect(0, 0, 1000, 2200), mActivity.getBounds());
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testApplyAspectRatio_activityCannotAlignWithParentAppVertical() {
        // The display's app bounds will be (0, 100, 1000, 2150)
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2300)
                .setCanRotate(false)
                .setCutout(0, 100, 0, 150)
                .build();

        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setMaxAspectRatio(2.1f)
                .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED));

        // The activity height is 2100 and the display's app bounds height is 2050, so the activity
        // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
        assertEquals(activity.getBounds(), display.getBounds());
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testApplyAspectRatio_activityAlignWithParentAppHorizontal() {
        // The display's app bounds will be (100, 0, 2350, 1000)
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2500, 1000)
                .setCanRotate(false)
                .setCutout(100, 0, 150, 0)
                .build();

        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setMaxAspectRatio(2.1f)
                .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
        // The activity width is 2100 and the display's app bounds width is 2250, so the activity
        // can be aligned inside parentAppBounds
        assertEquals(activity.getBounds(), new Rect(175, 0, 2275, 1000));
    }

    @Test
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testApplyAspectRatio_activityCannotAlignWithParentAppHorizontal() {
        // The display's app bounds will be (100, 0, 2150, 1000)
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2300, 1000)
                .setCanRotate(false)
                .setCutout(100, 0, 150, 0)
                .build();

        final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setMaxAspectRatio(2.1f)
                .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED));
        // The activity width is 2100 and the display's app bounds width is 2050, so the activity
        // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display
        assertEquals(activity.getBounds(), display.getBounds());
    }

    @Test
    public void testApplyAspectRatio_containingRatioAlmostEqualToMaxRatio_boundsUnchanged() {
        setUpDisplaySizeWithApp(1981, 2576);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);

        final Rect originalBounds = new Rect(mActivity.getBounds());
        prepareUnresizable(mActivity, 1.3f, SCREEN_ORIENTATION_UNSPECIFIED);

        // The containing aspect ratio is now 1.3003534, while the desired aspect ratio is 1.3. The
        // bounds of the activity should not be changed as the difference is too small
        assertEquals(mActivity.getBounds(), originalBounds);
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_activityFillParentWidth() {
        // Set up a display in landscape and ignoring orientation request.
        setUpLandscapeLargeScreenDisplayWithApp();
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        final Consumer<Float> assertHorizontalPosition = letterboxHorizontalPositionMultiplier -> {
            mActivity.mWmService.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
                    letterboxHorizontalPositionMultiplier);
            mActivity.recomputeConfiguration();
            assertFitted();
            // Rotate to put activity in size compat mode.
            rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
            assertTrue(mActivity.inSizeCompatMode());
            // Activity is in size compat mode but not scaled.
            assertEquals(new Rect(0, 0, 1400, 700), mActivity.getBounds());
            if (letterboxHorizontalPositionMultiplier < 1f) {
                rotateDisplay(mActivity.mDisplayContent, ROTATION_0);
            }
        };
        // When activity width equals parent width, multiplier shouldn't have any effect.
        assertHorizontalPosition.accept(0.0f);
        assertHorizontalPosition.accept(0.5f);
        assertHorizontalPosition.accept(1.0f);
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_topInsets_appCentered() {
        // Set up folded display
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 2100, 1100)
                .setCanRotate(true)
                .build();
        display.setIgnoreOrientationRequest(true);
        final DisplayPolicy policy = display.getDisplayPolicy();
        DisplayPolicy.DecorInsets.Info decorInfo = policy.getDecorInsetsInfo(ROTATION_90,
                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
        decorInfo.mNonDecorInsets.set(0, 130, 0, 60);
        spyOn(policy);
        doReturn(decorInfo).when(policy).getDecorInsetsInfo(ROTATION_90,
                display.mBaseDisplayHeight, display.mBaseDisplayWidth);
        mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);

        setUpApp(display);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        // Resize the display to simulate unfolding in portrait
        resizeDisplay(mTask.mDisplayContent, 1800, 2200);
        assertTrue(mActivity.inSizeCompatMode());

        // Simulate real display not taking non-decor insets into consideration
        display.getWindowConfiguration().setAppBounds(0, 0, 1800, 2200);

        // Rotate display to landscape
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        // App is centered
        assertEquals(mActivity.getBounds(), new Rect(50, 350, 2150, 1450));
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_top() {
        // Display configured as (1400, 2800).
        assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
                /* letterboxVerticalPositionMultiplier */ 0.0f,
                // At launch.
                /* fixedOrientationLetterbox */ new Rect(0, 0, 1400, 700),
                // After 90 degree rotation.
                /* sizeCompatUnscaled */ new Rect(700, 0, 2100, 700),
                // After the display is resized to (1400, 700).
                /* sizeCompatScaled */ new Rect(0, 0, 700, 350));
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_center() {
        // Display configured as (1400, 2800).
        assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
                /* letterboxVerticalPositionMultiplier */ 0.5f,
                // At launch.
                /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
                // After 90 degree rotation.
                /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
                // After the display is resized to (1400, 700).
                /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_bottom() {
        // Display configured as (1400, 2800).
        assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
                /* letterboxVerticalPositionMultiplier */ 1.0f,
                // At launch.
                /* fixedOrientationLetterbox */ new Rect(0, 2100, 1400, 2800),
                // After 90 degree rotation.
                /* sizeCompatUnscaled */ new Rect(700, 700, 2100, 1400),
                // After the display is resized to (1400, 700).
                /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400));
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_tabletop() {
        // Set up a display in portrait with a fixed-orientation LANDSCAPE app
        setUpDisplaySizeWithApp(1400, 2800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(
                1.0f /*letterboxVerticalPositionMultiplier*/);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        Rect letterboxNoFold = new Rect(0, 2100, 1400, 2800);
        assertEquals(letterboxNoFold, mActivity.getBounds());

        // Make the activity full-screen
        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);

        setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);

        Rect letterboxHalfFold = new Rect(0, 0, 1400, 700);
        assertEquals(letterboxHalfFold, mActivity.getBounds());

        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);

        assertEquals(letterboxNoFold, mActivity.getBounds());
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_unfoldDisplay_notTabletop() {
        // Set up a display in portrait with a fixed-orientation LANDSCAPE app.
        setUpDisplaySizeWithApp(1000, 2000);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(
                1.0f /*letterboxVerticalPositionMultiplier*/);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        // Make the activity full-screen.
        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);

        // Simulate display unfolding.
        setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);
        doReturn(true).when(mActivity.mDisplayContent).inTransition();
        resizeDisplay(mTask.mDisplayContent, 1400, 2800);

        // Make sure app doesn't jump to top (default tabletop position) when unfolding.
        assertEquals(1.0f, mActivity.mAppCompatController
                .getReachabilityOverrides().getVerticalPositionMultiplier(mActivity
                        .getParent().getConfiguration()), 0);

        // Simulate display fully open after unfolding.
        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
        doReturn(false).when(mActivity.mDisplayContent).inTransition();

        assertEquals(1.0f, mActivity.mAppCompatController
                .getReachabilityOverrides().getVerticalPositionMultiplier(mActivity
                        .getParent().getConfiguration()), 0);
    }

    @Test
    public void testGetFixedOrientationLetterboxAspectRatio_tabletop_centered() {
        // Set up a display in portrait with a fixed-orientation LANDSCAPE app
        setUpDisplaySizeWithApp(1400, 2800);
        mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
                LETTERBOX_POSITION_MULTIPLIER_CENTER);
        mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(
                1.0f /*letterboxVerticalPositionMultiplier*/);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);

        Configuration parentConfig = mActivity.getParent().getConfiguration();

        final AppCompatAspectRatioOverrides aspectRatioOverrides =
                mActivity.mAppCompatController.getAspectRatioOverrides();
        float actual = aspectRatioOverrides.getFixedOrientationLetterboxAspectRatio(parentConfig);
        float expected = aspectRatioOverrides.getSplitScreenAspectRatio();

        assertEquals(expected, actual, DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    public void testPortraitAppInTabletop_notSplitScreen() {
        final int dw = 2400;
        setUpDisplaySizeWithApp(dw, 2000);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        final int initialWidth = mActivity.getBounds().width();

        setFoldablePosture(true /* isHalfFolded */, true /* isTabletop */);

        final int finalWidth = mActivity.getBounds().width();
        assertEquals(initialWidth, finalWidth);
        assertNotEquals(finalWidth, getExpectedSplitSize(dw));
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_bookModeEnabled() {
        // Set up a display in landscape with a fixed-orientation PORTRAIT app
        setUpLandscapeLargeScreenDisplayWithApp();
        mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(true);
        mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(
                1.0f /*letterboxHorizontalPositionMultiplier*/);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        Rect letterboxNoFold = new Rect(2100, 0, 2800, 1400);
        assertEquals(letterboxNoFold, mActivity.getBounds());

        // Make the activity full-screen
        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);

        setFoldablePosture(true /* isHalfFolded */, false /* isTabletop */);

        Rect letterboxHalfFold = new Rect(0, 0, 700, 1400);
        assertEquals(letterboxHalfFold, mActivity.getBounds());

        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);

        assertEquals(letterboxNoFold, mActivity.getBounds());
    }

    @Test
    public void testUpdateResolvedBoundsHorizontalPosition_bookModeDisabled_centered() {
        // Set up a display in landscape with a fixed-orientation PORTRAIT app
        setUpLandscapeLargeScreenDisplayWithApp();
        mWm.mAppCompatConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false);
        mWm.mAppCompatConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
        prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);

        Rect letterboxNoFold = new Rect(1000, 0, 1800, 1400);
        assertEquals(letterboxNoFold, mActivity.getBounds());

        // Make the activity full-screen
        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);

        // Stay centered and bounds don't change
        setFoldablePosture(true /* isHalfFolded */, false /* isTabletop */);
        assertEquals(letterboxNoFold, mActivity.getBounds());

        setFoldablePosture(false /* isHalfFolded */, false /* isTabletop */);
        assertEquals(letterboxNoFold, mActivity.getBounds());
    }

    private void setFoldablePosture(ActivityRecord activity, boolean isHalfFolded,
            boolean isTabletop) {
        final DisplayRotation r = activity.mDisplayContent.getDisplayRotation();
        doReturn(isHalfFolded).when(r).isDisplaySeparatingHinge();
        doReturn(false).when(r).isDeviceInPosture(any(DeviceStateEnum.class), anyBoolean());
        if (isHalfFolded) {
            doReturn(true).when(r)
                    .isDeviceInPosture(DeviceStateEnum.HALF_FOLDED, isTabletop);
        }
        activity.recomputeConfiguration();
    }

    private void setFoldablePosture(boolean isHalfFolded, boolean isTabletop) {
        setFoldablePosture(mActivity, isHalfFolded, isTabletop);
    }

    @Test
    public void testUpdateResolvedBoundsPosition_alignToTop() {
        final int notchHeight = 100;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
                .setNotch(notchHeight)
                .build();
        setUpApp(display);

        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        // Prepare unresizable activity with max aspect ratio
        prepareUnresizable(mActivity, 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);

        Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
        // The insets should be cut for aspect ratio and then added back because the appBounds
        // are aligned to the top of the parentAppBounds
        assertEquals(new Rect(0, notchHeight, 1000, 1200), appBounds);
        assertEquals(new Rect(0, 0, 1000, 1200), bounds);

    }

    @Test
    public void testInsetOverrideNotAppliedInFreeform() {
        final int notchHeight = 100;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2800)
                .setNotch(notchHeight)
                .build();
        setUpApp(display);

        // Simulate inset override for legacy app bound behaviour
        mActivity.mResolveConfigHint.mUseOverrideInsetsForConfig = true;
        // Set task as freeform
        mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        Rect bounds = new Rect(mActivity.getWindowConfiguration().getBounds());
        Rect appBounds = new Rect(mActivity.getWindowConfiguration().getAppBounds());
        // App bounds should not include insets and should match bounds when in freeform.
        assertEquals(new Rect(0, 0, 1000, 2800), appBounds);
        assertEquals(new Rect(0, 0, 1000, 2800), bounds);
    }

    @Test
    @EnableFlags(Flags.FLAG_EXCLUDE_CAPTION_FROM_APP_BOUNDS)
    @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED})
    public void testInFreeform_boundsSandboxedToAppBounds() {
        allowDesktopMode();
        final int dw = 2800;
        final int dh = 1400;
        final int notchHeight = 100;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh)
                .setNotch(notchHeight)
                .build();
        setUpApp(display);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        mTask.mDisplayContent.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        mTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
        Rect appBounds = new Rect(0, 0, 1000, 500);
        Rect bounds = new Rect(0, 0, 1000, 600);
        mTask.getWindowConfiguration().setAppBounds(appBounds);
        mTask.getWindowConfiguration().setBounds(bounds);
        mActivity.onConfigurationChanged(mTask.getConfiguration());

        // Bounds are sandboxed to appBounds in freeform.
        assertDownScaled();
        assertEquals(mActivity.getWindowConfiguration().getAppBounds(),
                mActivity.getWindowConfiguration().getBounds());

        // Exit freeform.
        mTask.mDisplayContent.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
        mTask.getWindowConfiguration().setBounds(new Rect(0, 0, dw, dh));
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertFitted();
        appBounds = mActivity.getWindowConfiguration().getAppBounds();
        bounds = mActivity.getWindowConfiguration().getBounds();
        // Bounds are not sandboxed to appBounds.
        assertNotEquals(appBounds, bounds);
        assertEquals(notchHeight, appBounds.top - bounds.top);
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_UPSCALING_SIZE_COMPAT_ON_EXITING_DESKTOP_BUGFIX)
    public void testUpscaling_boundsUpscaledWithWindowingModeChange() {
        allowDesktopMode();

        // Launch an SCM app in freeform on an external display.
        final int dw = 2000;
        final int dh = 1600;
        final DisplayInfo displayInfo = new DisplayInfo();
        displayInfo.copyFrom(mDisplayInfo);
        displayInfo.type = TYPE_EXTERNAL;
        displayInfo.displayId = DEFAULT_DISPLAY + 1;
        displayInfo.logicalWidth = dw;
        displayInfo.logicalHeight = dh;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, displayInfo).build();
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpApp(display, null /* appBuilder */, taskBuilder);
        Rect bounds = new Rect(0, 0, 600, 800);
        mTask.setBounds(bounds);
        mTask.onConfigurationChanged(mTask.getDisplayArea().getConfiguration());
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        assertEquals(WindowConfiguration.WINDOWING_MODE_FREEFORM, mTask.getWindowingMode());
        assertFitted();

        // Exit freeform into fullscreen.
        display.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
        mTask.setBounds(null);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertUpScaled();

        // Move to a fullscreen, ignore-orientation-request, internal display.
        final DisplayInfo internalDisplayInfo = new DisplayInfo();
        internalDisplayInfo.copyFrom(display.getDisplayInfo());
        internalDisplayInfo.type = TYPE_INTERNAL;
        internalDisplayInfo.displayId = display.getDisplayInfo().displayId + 1;
        final DisplayContent internalDisplay =
                new TestDisplayContent.Builder(mAtm, internalDisplayInfo).build();
        mTask.mWmService.mRoot.moveRootTaskToDisplay(mTask.mTaskId, internalDisplay.mDisplayId,
                true /* onTop */);
        internalDisplay.setIgnoreOrientationRequest(true);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertTrue(mActivity.inSizeCompatMode());
        assertEquals(1f, mActivity.getCompatScale(), DELTA_COMPAT_SCALE_TOLERANCE);

        // Make the display not ignore-orientation-request.
        internalDisplay.setIgnoreOrientationRequest(false);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertUpScaled();

        // Make the display ignore-orientation-request again.
        internalDisplay.setIgnoreOrientationRequest(true);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertEquals(1f, mActivity.getCompatScale(), DELTA_COMPAT_SCALE_TOLERANCE);

        // Make the display desktop-first.
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        display.getDefaultTaskDisplayArea()
                .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
        assertUpScaled();
    }

    @Test
    @EnableFlags(Flags.FLAG_ENABLE_UPSCALING_SIZE_COMPAT_ON_EXITING_DESKTOP_BUGFIX)
    public void testUpscaling_boundsUpscaledWithDisplayMove() {
        final int dw = 1000;
        final int dh = 600;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh).build();
        setUpApp(display);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        assertEquals(WindowConfiguration.WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
        assertFitted();

        final DisplayInfo displayInfo = new DisplayInfo();
        displayInfo.copyFrom(mDisplayInfo);
        displayInfo.type = TYPE_EXTERNAL;
        displayInfo.logicalWidth = dw * 2;
        displayInfo.logicalHeight = dh * 2;
        final DisplayContent biggerExternalDisplay =
                new TestDisplayContent.Builder(mAtm, displayInfo).build();
        mActivity.mRootWindowContainer.moveRootTaskToDisplay(mTask.mTaskId,
                biggerExternalDisplay.mDisplayId, true /* onTop */);
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        assertUpScaled();
    }

    @Test
    @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
    public void testUserAspectRatioOverridesNotAppliedToResizeableFreeformActivity() {
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpDisplaySizeWithApp(2500, 1600, taskBuilder);

        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        spyOn(mActivity.mWmService.mAppCompatConfiguration);
        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
                .isUserAppAspectRatioSettingsEnabled();
        final AppCompatController appCompatController = mActivity.mAppCompatController;
        final AppCompatAspectRatioOverrides aspectRatioOverrides =
                appCompatController.getAspectRatioOverrides();
        spyOn(aspectRatioOverrides);
        // Set user aspect ratio override.
        doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
                .getUserMinAspectRatioOverrideCode();

        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
        assertFalse(appCompatController.getAspectRatioPolicy().isAspectRatioApplied());
    }

    @Test
    @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
    public void testUserAspectRatioOverridesAppliedToNonResizeableFreeformActivity() {
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpDisplaySizeWithApp(2500, 1600, taskBuilder);

        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        spyOn(mActivity.mWmService.mAppCompatConfiguration);
        doReturn(true).when(mActivity.mWmService.mAppCompatConfiguration)
                .isUserAppAspectRatioSettingsEnabled();
        final AppCompatController appCompatController = mActivity.mAppCompatController;
        final AppCompatAspectRatioOverrides aspectRatioOverrides =
                appCompatController.getAspectRatioOverrides();
        spyOn(aspectRatioOverrides);
        // Set user aspect ratio override.
        doReturn(USER_MIN_ASPECT_RATIO_16_9).when(aspectRatioOverrides)
                .getUserMinAspectRatioOverrideCode();

        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ true);
        assertTrue(appCompatController.getAspectRatioPolicy().isAspectRatioApplied());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
    public void testSystemAspectRatioOverridesNotAppliedToResizeableFreeformActivity() {
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);

        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied());
    }

    @Test
    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
    @EnableFlags(Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES)
    public void testSystemAspectRatioOverridesAppliedToNonResizeableFreeformActivity() {
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
        prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ true);

        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy().isAspectRatioApplied());
    }

    private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
            float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox,
            Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
        // Set up a display in portrait and ignoring orientation request.
        setUpPortraitLargeScreenDisplayWithApp();

        mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(
                letterboxVerticalPositionMultiplier);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        assertEquals(fixedOrientationLetterbox, mActivity.getBounds());

        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        assertTrue(mActivity.inSizeCompatMode());
        // Activity is in size compat mode but not scaled.
        assertEquals(sizeCompatUnscaled, mActivity.getBounds());

        // Force activity to scaled down for size compat mode.
        resizeDisplay(mTask.mDisplayContent, 1400, 700);

        assertTrue(mActivity.inSizeCompatMode());
        assertDownScaled();
        assertEquals(sizeCompatScaled, mActivity.getBounds());
    }

    @Test
    public void testUpdateResolvedBoundsVerticalPosition_activityFillParentHeight() {
        // When activity height equals parent height, multiplier shouldn't have any effect.
        assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
                /* letterboxVerticalPositionMultiplier */ 0.0f);
        assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
                /* letterboxVerticalPositionMultiplier */ 0.5f);
        assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
                /* letterboxVerticalPositionMultiplier */ 1.0f);
    }

    @Test
    public void testAreBoundsLetterboxed_letterboxedForAspectRatio_returnsTrue() {
        setUpDisplaySizeWithApp(1000, 2500);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        prepareUnresizable(mActivity, /* maxAspect= */ 2, SCREEN_ORIENTATION_PORTRAIT);
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());

        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
        rotateDisplay(mActivity.mDisplayContent, ROTATION_0);

        // After returning to the original rotation, bounds are computed in
        // ActivityRecord#resolveSizeCompatModeConfiguration because mCompatDisplayInsets aren't
        // null but activity doesn't enter size compat mode. Checking that areBoundsLetterboxed()
        // still returns true because of the aspect ratio restrictions.
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);

        // After setting the visibility of the activity to false, areBoundsLetterboxed() still
        // returns true but the NOT_VISIBLE App Compat state is logged.
        mActivity.setVisibility(false);
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE);
        mActivity.setVisibility(true);
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
    }

    @Test
    public void testAreBoundsLetterboxed_letterboxedForFixedOrientation_returnsTrue() {
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION);
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testIsLetterboxedForSafeRegionOnlyAllowed_noManifestProperty_returnsTrue() {
        setUpLandscapeLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);

        // For an activity letterboxed only due to safe region, areBoundsLetterboxed will return
        // false
        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);
        // Since no manifest property is defined, the activity is opted in by default
        assertTrue(mActivity.mAppCompatController.getSafeRegionPolicy()
                .isLetterboxedForSafeRegionOnlyAllowed());
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForActivity_returnsTrue() {
        setUpLandscapeLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);

        // Activity can opt-out the safe region letterboxing by component level property.
        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
        final PackageManager pm = mContext.getPackageManager();
        spyOn(pm);
        updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);
        updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
        final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
                .setComponent(name).setTask(mTask).build();
        optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);

        // Since activity manifest property is defined as true, the activity can be letterboxed
        // for safe region
        assertTrue(optOutActivity.mAppCompatController
                .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testIsLetterboxedForSafeRegionOnly_appPropUnset_allowedForActivity_returnsTrue()
            throws PackageManager.NameNotFoundException {
        setUpLandscapeLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);

        // Activity can opt-out the safe region letterboxing by component level property.
        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
        final PackageManager pm = mContext.getPackageManager();
        spyOn(pm);
        throwExceptionApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm);
        updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);

        final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
                .setComponent(name).setTask(mTask).build();
        optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);

        // Since activity manifest property is defined as true, the activity can be letterboxed
        // for safe region
        assertTrue(optOutActivity.mAppCompatController
                .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForActivity_returnsFalse() {
        setUpLandscapeLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);

        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
        final PackageManager pm = mContext.getPackageManager();
        spyOn(pm);
        updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
        updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
        final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
                .setComponent(name).setTask(mTask).build();
        optOutActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);

        // Since activity manifest property is defined as false, the activity can not be letterboxed
        // for safe region
        assertFalse(optOutActivity.mAppCompatController
                .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testIsLetterboxedForSafeRegionOnlyAllowed_notAllowedForApplication_returnsFalse() {
        setUpLandscapeLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);

        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
        final PackageManager pm = mContext.getPackageManager();
        spyOn(pm);
        updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
        updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
        final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
                .setComponent(name).setTask(mTask).build();
        optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);

        // Since application manifest property is defined as false, the activity can not be
        // letterboxed for safe region
        assertFalse(optOutAppActivity.mAppCompatController
                .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testIsLetterboxedForSafeRegionOnlyAllowed_allowedForApplication_returnsTrue() {
        setUpLandscapeLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        setupSafeRegionBoundsParameters(/* dw */ 300, /* dh */ 200);

        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
        final PackageManager pm = mContext.getPackageManager();
        spyOn(pm);
        updateActivityLevelAllowSafeRegionLetterboxingProperty(name, pm, false /* value */);
        updateApplicationLevelAllowSafeRegionLetterboxingProperty(name, pm, true /* value */);
        final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
                .setComponent(name).setTask(mTask).build();
        optOutAppActivity.mAppCompatController.getSafeRegionPolicy().setNeedsSafeRegionBounds(true);

        // Since application manifest property is defined as true, the activity can be letterboxed
        // for safe region
        assertTrue(optOutAppActivity.mAppCompatController
                .getSafeRegionPolicy().isLetterboxedForSafeRegionOnlyAllowed());
    }

    private void throwExceptionApplicationLevelAllowSafeRegionLetterboxingProperty(
            ComponentName name,
            PackageManager pm) throws PackageManager.NameNotFoundException {
        PackageManager.NameNotFoundException e = new PackageManager.NameNotFoundException(
                "Application level property not set");
        doThrow(e).when(pm).getPropertyAsUser(
                WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
                name.getPackageName(), /* className */ null, /* userId */ 0);
    }

    private void updateApplicationLevelAllowSafeRegionLetterboxingProperty(ComponentName name,
            PackageManager pm, boolean propertyValueForApplication) {
        final PackageManager.Property propertyForApplication = new PackageManager.Property(
                "propertyName", /* value */ propertyValueForApplication, name.getPackageName(),
                name.getClassName());
        try {
            doReturn(propertyForApplication).when(pm).getPropertyAsUser(
                    WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
                    name.getPackageName(), /* className */ null, /* userId */ 0);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    private void updateActivityLevelAllowSafeRegionLetterboxingProperty(ComponentName name,
            PackageManager pm, boolean propertyValueForActivity) {
        final PackageManager.Property propertyForActivity = new PackageManager.Property(
                "propertyName", /* value */ propertyValueForActivity, name.getPackageName(),
                name.getClassName());
        try {
            doReturn(propertyForActivity).when(pm).getPropertyAsUser(
                    WindowManager.PROPERTY_COMPAT_ALLOW_SAFE_REGION_LETTERBOXING,
                    name.getPackageName(), name.getClassName(), /* userId */ 0);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndFixedOrientation_returnTrue() {
        setUpLandscapeLargeScreenDisplayWithApp();
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 500, /* dh */ 200);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        // Activity is letterboxed due to fixed orientation within the safe region
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION);
        assertFalse(mActivity.mAppCompatController.getSafeRegionPolicy()
                .isLetterboxedForSafeRegionOnlyAllowed());
        assertTrue(safeRegionBounds.contains(mActivity.getBounds()));
    }

    @Test
    @EnableFlags(Flags.FLAG_SAFE_REGION_LETTERBOXING_V1)
    public void testAreBoundsLetterboxed_letterboxedForSafeRegionAndAspectRatio_returnTrue() {
        setUpPortraitLargeScreenDisplayWithApp();

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        final Rect safeRegionBounds = setupSafeRegionBoundsParameters(/* dw */ 200, /* dh */ 300);

        prepareMinAspectRatio(mActivity, 16 / 9f, SCREEN_ORIENTATION_PORTRAIT);

        // Activity is letterboxed due to min aspect ratio within the safe region
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForAspectRatioOnly());
        assertFalse(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO);
        assertTrue(safeRegionBounds.contains(mActivity.getBounds()));
    }

    private Rect setupSafeRegionBoundsParameters(int dw, int dh) {
        final AppCompatController appCompatController = mActivity.mAppCompatController;
        final AppCompatSafeRegionPolicy safeRegionPolicy =
                appCompatController.getSafeRegionPolicy();
        safeRegionPolicy.setNeedsSafeRegionBounds(true);
        spyOn(mTask);
        final Rect safeRegionBounds = new Rect(100, 200, 100 + dw, 200 + dh);
        doReturn(safeRegionBounds).when(mTask).getSafeRegionBounds();
        return safeRegionBounds;
    }

    @Test
    public void testAreBoundsLetterboxed_letterboxedForSizeCompat_returnsTrue() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertFalse(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity, APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED);

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertTrue(mActivity.inSizeCompatMode());
        assertTrue(mActivity.areBoundsLetterboxed());
        verifyLogAppCompatState(mActivity,
                APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE);
    }

    @Test
    public void testIsEligibleForLetterboxEducation_educationNotEnabled_returnsFalse() {
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(false);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
    }

    @Test
    public void testIsEligibleForLetterboxEducation_notEligibleForFixedOrientation_returnsFalse() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
    }

    @Test
    public void testIsEligibleForLetterboxEducation_windowingModeMultiWindow_returnsFalse() {
        // Support non resizable in multi window
        mAtm.mDevEnableNonResizableMultiWindow = true;
        setUpDisplaySizeWithApp(1000, 1200);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true);
        final TestSplitOrganizer organizer =
                new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());

        // Non-resizable landscape activity
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        final Rect originalBounds = new Rect(mActivity.getBounds());

        // Move activity to split screen which takes half of the screen.
        mTask.reparent(organizer.mPrimary, POSITION_TOP,
                false /*moveParents*/, "test");
        organizer.mPrimary.setBounds(0, 0, 1000, 600);

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
        assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
    }

    @Test
    public void testIsEligibleForLetterboxEducation_fixedOrientationLandscape_returnsFalse() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
    }

    @Test
    public void testIsEligibleForLetterboxEducation_hasStartingWindow_returnsFalseUntilRemoved() {
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        mActivity.mStartingData = mock(StartingData.class);
        mActivity.attachStartingWindow(
                createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
                        mActivity));

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());

        // Verify that after removing the starting window isEligibleForLetterboxEducation returns
        // true and mTask.dispatchTaskInfoChangedIfNeeded is called.
        spyOn(mTask);
        mActivity.removeStartingWindow();

        assertTrue(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
        verify(mTask).dispatchTaskInfoChangedIfNeeded(true);
    }

    @Test
    public void testIsEligibleForLetterboxEducation_hasStartingWindowAndEducationNotEnabled() {
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(false);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        mActivity.mStartingData = mock(StartingData.class);
        mActivity.attachStartingWindow(
                createWindowState(new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING),
                        mActivity));

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());

        // Verify that after removing the starting window isEligibleForLetterboxEducation still
        // returns false and mTask.dispatchTaskInfoChangedIfNeeded isn't called.
        spyOn(mTask);
        mActivity.removeStartingWindow();

        assertFalse(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
        verify(mTask, never()).dispatchTaskInfoChangedIfNeeded(true);
    }

    @Test
    public void testIsEligibleForLetterboxEducation_letterboxedForFixedOrientation_returnsTrue() {
        setUpDisplaySizeWithApp(2500, 1000);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertTrue(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
        assertTrue(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
    }

    @Test
    public void testIsEligibleForLetterboxEducation_sizeCompatAndEligibleForFixedOrientation() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
        mActivity.mWmService.mAppCompatConfiguration.setIsEducationEnabled(true);

        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        assertTrue(mActivity.mAppCompatController.getLetterboxPolicy()
                .isEligibleForLetterboxEducation());
        assertFalse(mActivity.mAppCompatController.getAspectRatioPolicy()
                .isLetterboxedForFixedOrientationAndAspectRatio());
        assertTrue(mActivity.inSizeCompatMode());
    }

    @Test
    public void testTopActivityInSizeCompatMode_pausedAndInSizeCompatMode_returnsTrue() {
        setUpDisplaySizeWithApp(1000, 2500);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        spyOn(mActivity);
        doReturn(mTask).when(mActivity).getOrganizedTask();
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
        mActivity.setState(PAUSED, "test");

        assertTrue(mActivity.inSizeCompatMode());
        assertEquals(mActivity.getState(), PAUSED);
        assertTrue(mActivity.isVisible());
        assertTrue(mTask.getTaskInfo().appCompatTaskInfo.isTopActivityInSizeCompat());
    }

    @Test
    public void testParentRotationIgnoredForSCMActivityInDesktopMode() {
        final TaskBuilder taskBuilder =
                new TaskBuilder(mSupervisor).setWindowingMode(WINDOWING_MODE_FREEFORM);
        setUpDisplaySizeWithApp(2500, 1600, taskBuilder);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
        assertFitted();

        // Simulate entering desktop mode by changing app to freeform and reducing size.
        mTask.getWindowConfiguration().setWindowingMode(WINDOWING_MODE_FREEFORM);
        mTask.getWindowConfiguration().setAppBounds(new Rect(0, 0, 500, 1250));
        mActivity.onConfigurationChanged(mTask.getConfiguration());
        // Activity should now be in SCM mode.
        assertDownScaled();

        final int originalRotation =
                mActivity.getResolvedOverrideConfiguration().windowConfiguration.getRotation();
        rotateDisplay(mActivity.mDisplayContent, ROTATION_180);
        // Resolved orientation should match original orientation before device was rotated.
        assertEquals(originalRotation,
                mActivity.getResolvedOverrideConfiguration().windowConfiguration.getRotation());
    }

    /**
     * Tests that all three paths in which aspect ratio logic can be applied yield the same
     * result, which is that aspect ratio is respected on app bounds. The three paths are
     * fixed orientation, no fixed orientation but fixed aspect ratio, and size compat mode.
     */
    @Test
    public void testAllAspectRatioLogicConsistent() {
        // Create display that has all stable insets and does not rotate. Make sure that status bar
        // height is greater than notch height so that stable bounds do not equal app bounds.
        final int notchHeight = 75;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
                .setSystemDecorations(true).setNotch(notchHeight)
                .setStatusBarHeight(notchHeight + 20).setCanRotate(false).build();

        // Create task on test display.
        final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();

        // Target min aspect ratio must be larger than parent aspect ratio to be applied.
        final float targetMinAspectRatio = 3.0f;

        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
        final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setMinAspectRatio(targetMinAspectRatio).build();
        final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        // Create activity with no fixed orientation and min aspect ratio greater than parent aspect
        // ratio.
        final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm).setTask(task)
                .setMinAspectRatio(targetMinAspectRatio).build();
        final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        // Create unresizeable fixed portrait activity with min aspect ratio greater than parent
        // aspect ratio.
        final ActivityRecord sizeCompatActivity = new ActivityBuilder(mAtm)
                .setTask(task).setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setMinAspectRatio(targetMinAspectRatio).build();
        // Resize display running unresizeable activity to make it enter size compat mode.
        resizeDisplay(display, 1800, 1000);
        final Rect sizeCompatAppBounds = new Rect(sizeCompatActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        // Check that aspect ratio of app bounds is equal to the min aspect ratio.
        assertEquals(targetMinAspectRatio,
                computeAspectRatio(fixedOrientationAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
        assertEquals(targetMinAspectRatio,
                computeAspectRatio(minAspectRatioAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
        assertEquals(targetMinAspectRatio,
                computeAspectRatio(sizeCompatAppBounds), DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
    public void testCameraCompatAspectRatioAppliedForFixedOrientationCameraActivities() {
        // Needed to create camera compat policy in DisplayContent.
        allowDesktopMode();
        // Create display that has all stable insets and does not rotate.
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
                .setSystemDecorations(true).setCanRotate(false).build();

        final float cameraCompatAspectRatio = 4.0f;
        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);

        // Create task on test display.
        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();

        // Create fixed portrait activity.
        final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
        final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
    public void testCameraCompatAspectRatioForFixedOrientationCameraActivitiesPortraitWindow() {
        // Needed to create camera compat policy in DisplayContent.
        allowDesktopMode();
        // Create portrait display that has all stable insets and does not rotate.
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 1600)
                .setSystemDecorations(true).setCanRotate(false).build();

        final float cameraCompatAspectRatio = 4.0f;
        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);

        // Create task on test display.
        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();

        // Create fixed portrait activity.
        final ActivityRecord fixedOrientationActivity = new ActivityBuilder(mAtm)
                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).build();
        final Rect fixedOrientationAppBounds = new Rect(fixedOrientationActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        assertEquals(cameraCompatAspectRatio, computeAspectRatio(fixedOrientationAppBounds),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
    public void testCameraCompatAspectRatioAppliedInsteadOfDefaultAspectRatio() {
        // Needed to create camera compat policy in DisplayContent.
        allowDesktopMode();
        // Create display that has all stable insets and does not rotate.
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
                .setSystemDecorations(true).setCanRotate(false).build();

        final float cameraCompatAspectRatio = 5.0f;
        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);

        // Create task on test display.
        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();

        // App's target min aspect ratio - this should not be used, as camera controls aspect ratio.
        final float targetMinAspectRatio = 4.0f;

        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
        final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setMinAspectRatio(targetMinAspectRatio).build();
        final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        assertEquals(cameraCompatAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    @EnableFlags({Flags.FLAG_IGNORE_ASPECT_RATIO_RESTRICTIONS_FOR_RESIZEABLE_FREEFORM_ACTIVITIES,
            Flags.FLAG_ENABLE_CAMERA_COMPAT_FOR_DESKTOP_WINDOWING})
    public void testCameraCompatAspectRatio_defaultAspectRatioAppliedWhenGreater() {
        // Needed to create camera compat policy in DisplayContent.
        allowDesktopMode();
        // Create display that has all stable insets and does not rotate.
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1080, 600)
                .setSystemDecorations(true).setCanRotate(false).build();

        final float cameraCompatAspectRatio = 5.0f;
        setupCameraCompatAspectRatio(cameraCompatAspectRatio, display);

        // Create task on test display.
        final Task task = new TaskBuilder(mSupervisor).setDisplay(display)
                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();

        // App's target min aspect ratio bigger than camera compat aspect ratio - use that instead.
        final float targetMinAspectRatio = 6.0f;

        // Create fixed portrait activity with min aspect ratio greater than parent aspect ratio.
        final ActivityRecord minAspectRatioActivity = new ActivityBuilder(mAtm)
                .setTask(task).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)
                .setMinAspectRatio(targetMinAspectRatio).build();
        final Rect minAspectRatioAppBounds = new Rect(minAspectRatioActivity.getConfiguration()
                .windowConfiguration.getAppBounds());

        assertEquals(targetMinAspectRatio, computeAspectRatio(minAspectRatioAppBounds),
                DELTA_ASPECT_RATIO_TOLERANCE);
    }

    @Test
    public void testUniversalResizeable() {
        mWm.mConstants.mIgnoreActivityOrientationRequestSmallScreen = true;
        mWm.mConstants.mIgnoreActivityOrientationRequestLargeScreen = true;
        setUpApp(mDisplayContent);
        final float maxAspect = 1.8f;
        final float minAspect = 1.5f;
        prepareLimitedBounds(mActivity, maxAspect, minAspect,
                ActivityInfo.SCREEN_ORIENTATION_NOSENSOR, true /* isUnresizable */);

        assertTrue(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
                mWm, true /* isLargeScreen */, false /* forActivity */));
        assertTrue(mActivity.isUniversalResizeable());
        assertTrue(mActivity.isResizeable());
        assertFalse(mActivity.shouldCreateAppCompatDisplayInsets());
        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());
        assertEquals(mActivity.getTask().getBounds(), mActivity.getBounds());
        final AppCompatAspectRatioPolicy aspectRatioPolicy = mActivity.mAppCompatController
                .getAspectRatioPolicy();
        assertEquals(0, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
        assertEquals(0, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);

        // Compat override can still take effect.
        final AppCompatAspectRatioOverrides aspectRatioOverrides =
                mActivity.mAppCompatController.getAspectRatioOverrides();
        spyOn(aspectRatioOverrides);
        doReturn(true).when(aspectRatioOverrides).shouldOverrideMinAspectRatio();
        assertEquals(minAspect, aspectRatioPolicy.getMinAspectRatio(), 0 /* delta */);

        // User override can still take effect.
        doReturn(USER_MIN_ASPECT_RATIO_3_2).when(aspectRatioOverrides)
                .getUserMinAspectRatioOverrideCode();
        assertFalse(mActivity.isResizeable());
        assertEquals(maxAspect, aspectRatioPolicy.getMaxAspectRatio(), 0 /* delta */);
        assertNotEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());

        // Activity can opt-out the resizability by component level property.
        final ComponentName name = getUniqueComponentName(mContext.getPackageName());
        final PackageManager pm = mContext.getPackageManager();
        spyOn(pm);
        final PackageManager.Property property = new PackageManager.Property("propertyName",
                true /* value */, name.getPackageName(), name.getClassName());
        // Activity level.
        try {
            doReturn(property).when(pm).getPropertyAsUser(
                    WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
                    name.getPackageName(), name.getClassName(), 0 /* userId */);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        final ActivityRecord optOutActivity = new ActivityBuilder(mAtm)
                .setComponent(name).setTask(mTask).build();
        assertFalse(optOutActivity.isUniversalResizeable());

        // Application level.
        try {
            doReturn(property).when(pm).getPropertyAsUser(
                    WindowManager.PROPERTY_COMPAT_ALLOW_RESTRICTED_RESIZABILITY,
                    name.getPackageName(), null /* className */, 0 /* userId */);
        } catch (PackageManager.NameNotFoundException e) {
            throw new RuntimeException(e);
        }
        final ActivityRecord optOutAppActivity = new ActivityBuilder(mAtm)
                .setComponent(getUniqueComponentName(mContext.getPackageName()))
                .setTask(mTask).build();
        assertFalse(optOutAppActivity.isUniversalResizeable());
        assertFalse(ActivityRecord.canBeUniversalResizeable(mActivity.info.applicationInfo,
                mWm, true /* isLargeScreen */, false /* forActivity */));
    }


    @Test
    @EnableCompatChanges({ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT})
    public void testUniversalResizeableByDefault() {
        mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT);
        mDisplayContent.setIgnoreOrientationRequest(false);
        setUpApp(mDisplayContent, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
                .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE));
        assertFalse(mActivity.isUniversalResizeable());
        mActivity.mAppCompatController.getSizeCompatModePolicy().updateAppCompatDisplayInsets();
        assertNotNull(mActivity.getAppCompatDisplayInsets());

        mDisplayContent.setIgnoreOrientationRequest(true);
        makeDisplayLargeScreen(mDisplayContent);
        assertTrue(mActivity.isUniversalResizeable());
        assertFitted();

        // Though "locked" orientation is respected to keep current display orientation, it should
        // not be treated as fixed in portrait or landscape.
        setUpApp(mDisplayContent, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED));
        assertEquals(ActivityInfo.SCREEN_ORIENTATION_LOCKED, mActivity.getOverrideOrientation());
        assertEquals("Must not be a specific orientation", Configuration.ORIENTATION_UNDEFINED,
                mActivity.getRequestedConfigurationOrientation());
        final Rect resizeBounds = new Rect(mActivity.getTask().getBounds());
        resizeBounds.scale(0.8f);
        mActivity.getTask().setBounds(resizeBounds);
        assertFitted();
    }

    @Test
    @EnableCompatChanges({ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT})
    public void testRestrictedResizabilityOptOutNotOverrideToUser() {
        mDisplayContent.setIgnoreOrientationRequest(true);
        makeDisplayLargeScreen(mDisplayContent);
        setUpApp(mDisplayContent, new ActivityBuilder(mAtm)
                .setResizeMode(RESIZE_MODE_UNRESIZEABLE)
                .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT));

        assertEquals(SCREEN_ORIENTATION_UNSPECIFIED, mActivity.getOverrideOrientation());

        final AppCompatAspectRatioOverrides overrides =
                mActivity.mAppCompatController.getAspectRatioOverrides();
        final MockitoSession session = mockitoSession().spyStatic(AppCompatUtils.class)
                .strictness(org.mockito.quality.Strictness.LENIENT).startMocking();
        try {
            doReturn(true).when(() -> AppCompatUtils.isChangeEnabled(
                    eq(mActivity), eq(ActivityInfo.OVERRIDE_ANY_ORIENTATION_TO_USER)));
            overrides.resetSystemFullscreenOverrideCache();

            assertTrue(overrides.isSystemOverrideToFullscreenEnabled());
            if (!com.android.window.flags.Flags.optOutOverrideOrientationToUser()) {
                return;
            }

            final AppCompatResizeOverrides resizeOverrides =
                    mActivity.mAppCompatController.getResizeOverrides();
            spyOn(resizeOverrides);
            doReturn(true).when(resizeOverrides).allowRestrictedResizability();
            overrides.resetSystemFullscreenOverrideCache();

            assertFalse(overrides.isSystemOverrideToFullscreenEnabled());
            assertEquals(SCREEN_ORIENTATION_PORTRAIT, mActivity.getOverrideOrientation());
        } finally {
            session.finishMocking();
        }
    }

    @Test
    public void testClearSizeCompat_resetOverrideConfig() {
        final int origDensity = 480;
        final int newDensity = 520;
        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 600, 800)
                .setDensityDpi(origDensity)
                .build();
        setUpApp(display);
        prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);

        // Activity should enter size compat with old density after display density change.
        display.setForcedDensity(newDensity, UserHandle.USER_CURRENT);

        assertDownScaled();
        assertEquals(origDensity, mActivity.getConfiguration().densityDpi);

        // Activity should exit size compat with new density.
        mActivity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();

        assertFitted();
        assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
    }

    /**
     * {@code canEnterDesktopMode} is called when {@link AppCompatCameraSimReqOrientationPolicy}
     * is created in {@link AppCompatCameraPolicy}.
     *
     * <p>{@link #allowDesktopMode()} needs to be called before {@link DisplayContent} is created.
     */
    private void allowDesktopMode() {
        doReturn(true).when(() -> DesktopModeHelper.canEnterDesktopMode(any()));
    }

    private void setupCameraCompatAspectRatio(float cameraCompatAspectRatio,
            @NonNull DisplayContent display) {
        AppCompatCameraSimReqOrientationPolicy cameraPolicy = display.mAppCompatCameraPolicy
                .mSimReqOrientationPolicy;
        spyOn(cameraPolicy);
        doReturn(true).when(cameraPolicy).shouldCameraCompatControlAspectRatio(any());
        doReturn(cameraCompatAspectRatio).when(cameraPolicy).getCameraCompatAspectRatio(any());
    }

    private void setUpAllowThinLetterboxed(boolean thinLetterboxAllowed) {
        final AppCompatReachabilityOverrides reachabilityOverrides =
                mActivity.mAppCompatController.getReachabilityOverrides();
        spyOn(reachabilityOverrides);
        doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
                .allowVerticalReachabilityForThinLetterbox();
        doReturn(thinLetterboxAllowed).when(reachabilityOverrides)
                .allowHorizontalReachabilityForThinLetterbox();
    }

    private int getExpectedSplitSize(int dimensionToSplit) {
        int dividerWindowWidth =
                mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
                        com.android.internal.R.dimen.docked_stack_divider_thickness);
        int dividerInsets =
                mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
                        com.android.internal.R.dimen.docked_stack_divider_insets);
        return (dimensionToSplit - (dividerWindowWidth - dividerInsets * 2)) / 2;
    }

    private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
            float letterboxVerticalPositionMultiplier) {
        // Set up a display in portrait and ignoring orientation request.
        setUpDisplaySizeWithApp(1400, 2800);
        mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);

        mActivity.mWmService.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(
                letterboxVerticalPositionMultiplier);
        prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);

        assertFitted();

        // Rotate to put activity in size compat mode.
        rotateDisplay(mActivity.mDisplayContent, ROTATION_90);

        assertTrue(mActivity.inSizeCompatMode());
        // Activity is in size compat mode but not scaled.
        assertEquals(new Rect(1050, 0, 1750, 1400), mActivity.getBounds());
    }

    private WindowState addWindowToActivity(ActivityRecord activity) {
        return addWindowToActivity(activity, WindowManager.LayoutParams.TYPE_BASE_APPLICATION);
    }

    private WindowState addWindowToActivity(ActivityRecord activity, int type) {
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
        params.type = type;
        params.setFitInsetsSides(0);
        params.setFitInsetsTypes(0);
        final TestWindowState w = new TestWindowState(
                activity.mWmService, getTestSession(), new TestIWindow(), params, activity);
        makeWindowVisible(w);
        w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
        activity.addWindow(w);
        return w;
    }

    private TestWindowState addStatusBar(DisplayContent displayContent) {
        final TestWindowToken token = createTestWindowToken(
                TYPE_STATUS_BAR, displayContent);
        final WindowManager.LayoutParams attrs =
                new WindowManager.LayoutParams(TYPE_STATUS_BAR);
        final Binder owner = new Binder();
        attrs.gravity = android.view.Gravity.TOP;
        attrs.height = STATUS_BAR_HEIGHT;
        attrs.layoutInDisplayCutoutMode =
                WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        attrs.setFitInsetsTypes(0 /* types */);
        attrs.providedInsets = new InsetsFrameProvider[] {
                new InsetsFrameProvider(owner, 0, WindowInsets.Type.statusBars()),
                new InsetsFrameProvider(owner, 0, WindowInsets.Type.tappableElement()),
                new InsetsFrameProvider(owner, 0, WindowInsets.Type.mandatorySystemGestures())
        };
        final TestWindowState statusBar = new TestWindowState(
                displayContent.mWmService, getTestSession(), new TestIWindow(), attrs, token);
        token.addWindow(statusBar);
        statusBar.setRequestedSize(displayContent.mBaseDisplayWidth,
                SystemBarUtils.getStatusBarHeight(displayContent.getDisplayUiContext()));

        final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
        displayPolicy.addWindowLw(statusBar, attrs);
        displayPolicy.layoutWindowLw(statusBar, null, displayContent.mDisplayFrames);
        return statusBar;
    }

    /**
     * Returns an ActivityRecord instance with the specified attributes on the same task. By
     * constructing the ActivityRecord, forces {@link ActivityInfo} to be loaded with the compat
     * config settings.
     */
    private ActivityRecord buildActivityRecord(boolean supportsSizeChanges, int resizeMode,
            @ScreenOrientation int screenOrientation) {
        return getActivityBuilderWithoutTask().setTask(mTask)
                .setResizeMode(resizeMode)
                .setSupportsSizeChanges(supportsSizeChanges)
                .setScreenOrientation(screenOrientation)
                .build();
    }

    private ActivityBuilder getActivityBuilderWithoutTask() {
        return new ActivityBuilder(mAtm)
                .setComponent(ComponentName.createRelative(mContext,
                        SizeCompatTests.class.getName()))
                .setUid(android.os.Process.myUid());
    }

    static void prepareMinAspectRatio(ActivityRecord activity, float minAspect,
            int screenOrientation) {
        prepareLimitedBounds(activity, -1 /* maxAspect */, minAspect, screenOrientation,
                true /* isUnresizable */);
    }

    static void prepareUnresizable(ActivityRecord activity, int screenOrientation) {
        prepareUnresizable(activity, -1 /* maxAspect */, screenOrientation);
    }

    static void prepareUnresizable(ActivityRecord activity, float maxAspect,
            int screenOrientation) {
        prepareLimitedBounds(activity, maxAspect, screenOrientation, true /* isUnresizable */);
    }

    static void prepareLimitedBounds(ActivityRecord activity, int screenOrientation,
            boolean isUnresizable) {
        prepareLimitedBounds(activity, -1 /* maxAspect */, screenOrientation, isUnresizable);
    }

    static void prepareLimitedBounds(ActivityRecord activity, float maxAspect,
            int screenOrientation, boolean isUnresizable) {
        prepareLimitedBounds(activity, maxAspect, -1 /* minAspect */, screenOrientation,
                isUnresizable);
    }

    /**
     * Setups {@link #mActivity} with restriction on its bounds, such as maxAspect, minAspect,
     * fixed orientation, and/or whether it is resizable.
     */
    static void prepareLimitedBounds(ActivityRecord activity, float maxAspect, float minAspect,
            int screenOrientation, boolean isUnresizable) {
        activity.info.resizeMode = isUnresizable
                ? RESIZE_MODE_UNRESIZEABLE
                : RESIZE_MODE_RESIZEABLE;
        final Task task = activity.getTask();
        if (task != null) {
            // Update the Task resize value as activity will follow the task.
            task.mResizeMode = activity.info.resizeMode;
            task.getRootActivity().info.resizeMode = activity.info.resizeMode;
        }
        activity.setVisibleRequested(true);
        if (maxAspect >= 0) {
            activity.info.setMaxAspectRatio(maxAspect);
        }
        if (minAspect >= 0) {
            activity.info.setMinAspectRatio(minAspect);
        }
        if (screenOrientation != SCREEN_ORIENTATION_UNSPECIFIED) {
            activity.info.screenOrientation = screenOrientation;
            activity.setRequestedOrientation(screenOrientation);
        }
        // Make sure to use the provided configuration to construct the size compat fields.
        activity.mAppCompatController.getSizeCompatModePolicy().clearSizeCompatMode();
        activity.ensureActivityConfiguration();
        // Make sure the display configuration reflects the change of activity.
        if (activity.mDisplayContent.updateOrientation()) {
            activity.mDisplayContent.sendNewConfiguration();
        }
    }

    private void assertUpScaled() {
        assertScaled(mActivity, /* upScalingExpected */ true);
    }

    private void assertDownScaled() {
        assertScaled(mActivity, /* upScalingExpected */ false);
    }

    /**
     * Asserts that the size of an activity differs from its parent and so it is scaling (either up
     * or down).
     */
    private void assertScaled(ActivityRecord activity, boolean upScalingExpected) {
        assertTrue(activity.inSizeCompatMode());
        if (upScalingExpected) {
            assertTrue(activity.getCompatScale() > 1f);
        } else {
            assertTrue(activity.getCompatScale() < 1f);
        }
    }

    private void assertFitted() {
        assertFitted(mActivity);
    }

    /** Asserts that the activity is best fitted in the parent. */
    private void assertFitted(ActivityRecord activity) {
        final boolean inSizeCompatMode = activity.inSizeCompatMode();
        final String failedConfigInfo = inSizeCompatMode
                ? ("ParentConfig=" + activity.getParent().getConfiguration()
                        + " ActivityConfig=" + activity.getConfiguration())
                : "";
        assertFalse(failedConfigInfo, inSizeCompatMode);
        assertFalse(activity.hasSizeCompatBounds());
    }

    /** Asserts the activity max bounds inherit from the TaskDisplayArea. */
    private void assertMaxBoundsInheritDisplayAreaBounds() {
        assertThat(mActivity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(mTask.getDisplayArea().getBounds());
    }

    /**
     * Asserts activity-level letterbox or size compat mode size compat mode, so activity max
     * bounds are sandboxed.
     */
    private void assertActivityMaxBoundsSandboxed() {
        assertActivityMaxBoundsSandboxed(mActivity);
    }

    /**
     * Asserts activity-level letterbox or size compat mode size compat mode on the specified
     * activity, so activity max bounds are sandboxed.
     */
    private void assertActivityMaxBoundsSandboxed(ActivityRecord activity) {
        // Activity max bounds are sandboxed due to size compat mode.
        assertThat(activity.getConfiguration().windowConfiguration.getMaxBounds())
                .isEqualTo(activity.getWindowConfiguration().getBounds());
    }

    private void verifyLogAppCompatState(ActivityRecord activity, int state) {
        verify(mActivityMetricsLogger, atLeastOnce()).logAppCompatState(
                argThat(r -> activity == r && r.getAppCompatState() == state));
    }

    static Configuration rotateDisplay(DisplayContent display, int rotation) {
        final Configuration c = new Configuration();
        display.getDisplayRotation().setRotation(rotation);
        display.computeScreenConfiguration(c);
        display.onRequestedOverrideConfigurationChanged(c);
        return c;
    }

    private static void setNeverConstrainDisplayApisFlag(@Nullable String value,
            boolean makeDefault) {
        DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
                CONFIG_NEVER_CONSTRAIN_DISPLAY_APIS, value, makeDefault);
    }

    private static void setNeverConstrainDisplayApisAllPackagesFlag(boolean value,
            boolean makeDefault) {
        DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
                CONFIG_NEVER_CONSTRAIN_DISPLAY_APIS_ALL_PACKAGES, String.valueOf(value),
                makeDefault);
    }

    private static void setAlwaysConstrainDisplayApisFlag(@Nullable String value,
            boolean makeDefault) {
        DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
                CONFIG_ALWAYS_CONSTRAIN_DISPLAY_APIS, value, makeDefault);
    }
}
