/*
 * Copyright (C) 2016 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 android.server.wm;

import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
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.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.server.wm.StateLogger.logE;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.WindowManagerState.dpToPx;
import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
import static android.server.wm.app.Components.DIALOG_WHEN_LARGE_ACTIVITY;
import static android.server.wm.app.Components.LANDSCAPE_ORIENTATION_ACTIVITY;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_APP_CONFIG_INFO;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_CONFIG_INFO_IN_ON_CREATE;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_DISPLAY_REAL_SIZE;
import static android.server.wm.app.Components.LandscapeOrientationActivity.EXTRA_SYSTEM_RESOURCES_CONFIG_INFO;
import static android.server.wm.app.Components.NIGHT_MODE_ACTIVITY;
import static android.server.wm.app.Components.PORTRAIT_ORIENTATION_ACTIVITY;
import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY;
import static android.server.wm.app.Components.TEST_ACTIVITY;
import static android.server.wm.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY;
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 org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.ActivitySessionClient;
import android.server.wm.CommandSession.ConfigInfo;
import android.server.wm.CommandSession.SizeInfo;
import android.server.wm.TestJournalProvider.TestJournalContainer;
import android.util.DisplayMetrics;
import android.view.Display;

import org.junit.Test;

import java.util.function.Function;

/**
 * Build/Install/Run:
 *     atest CtsWindowManagerDeviceTestCases:AppConfigurationTests
 */
@Presubmit
public class AppConfigurationTests extends MultiDisplayTestBase {

    private static final int SMALL_WIDTH_DP = 426;
    private static final int SMALL_HEIGHT_DP = 320;

    /**
     * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
     * has an updated size when the Activity is resized from fullscreen to docked state.
     *
     * The Activity handles configuration changes, so it will not be restarted between resizes.
     * On Configuration changes, the Activity logs the Display size and Configuration width
     * and heights. The values reported in fullscreen should be larger than those reported in
     * docked state.
     */
    @Test
    public void testConfigurationUpdatesWhenResizedFromFullscreen() {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        separateTestJournal();
        launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        final SizeInfo fullscreenSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        separateTestJournal();
        putActivityInPrimarySplit(RESIZEABLE_ACTIVITY);
        final SizeInfo dockedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        assertSizesAreSane(fullscreenSizes, dockedSizes);
    }

    /**
     * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
     * from docked state to fullscreen (reverse).
     */
    @Test
    public void testConfigurationUpdatesWhenResizedFromDockedStack() {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        separateTestJournal();
        launchActivitiesInSplitScreen(
                getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY),
                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
        final SizeInfo dockedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        separateTestJournal();
        dismissSplitScreen(true /* primaryOnTop */);
        final SizeInfo fullscreenSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        assertSizesAreSane(fullscreenSizes, dockedSizes);
    }

    /**
     * Tests whether the Display sizes change when rotating the device.
     */
    @Test
    public void testConfigurationUpdatesWhenRotatingWhileFullscreen() {
        assumeTrue("Skipping test: no rotation support", supportsRotation());

        final RotationSession rotationSession = createManagedRotationSession();
        rotationSession.set(ROTATION_0);

        separateTestJournal();
        final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
        resizeableActivityClient.startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setTargetActivity(RESIZEABLE_ACTIVITY)
                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN));
        final SizeInfo initialSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        rotateAndCheckSizes(rotationSession, resizeableActivityClient, initialSizes);
    }

    /**
     * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
     * is in the docked stack.
     */
    @Test
    public void testConfigurationUpdatesWhenRotatingWhileDocked() {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
        final RotationSession rotationSession = createManagedRotationSession();
        rotationSession.set(ROTATION_0);

        separateTestJournal();
        // Launch our own activity to side in case Recents (or other activity to side) doesn't
        // support rotation.
        launchActivitiesInSplitScreen(
                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
        // Launch target activity in docked stack.
        getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY)
                .setActivitySessionClient(resizeableActivityClient).execute();
        final SizeInfo initialSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        rotateAndCheckSizes(rotationSession, resizeableActivityClient, initialSizes);
    }

    /**
     * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
     * is launched to side from docked stack.
     */
    @Test
    public void testConfigurationUpdatesWhenRotatingToSideFromDocked() {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        final ActivitySessionClient resizeableActivityClient = createManagedActivityClientSession();
        final RotationSession rotationSession = createManagedRotationSession();
        rotationSession.set(ROTATION_0);

        separateTestJournal();
        launchActivitiesInSplitScreen(
                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
                getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY)
                        .setActivitySessionClient(resizeableActivityClient));
        final SizeInfo initialSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);

        rotateAndCheckSizes(rotationSession, resizeableActivityClient, initialSizes);
    }

    private void rotateAndCheckSizes(RotationSession rotationSession,
            ActivitySessionClient noRelaunchActivityClient, SizeInfo prevSizes) {
        final ActivitySession activitySession = noRelaunchActivityClient.getLastStartedSession();
        final ComponentName activityName = activitySession.getName();
        final WindowManagerState.ActivityTask task = mWmState.getTaskByActivity(activityName);
        final int displayId = mWmState.getRootTask(task.mRootTaskId).mDisplayId;

        assumeTrue(supportsLockedUserRotation(rotationSession, displayId));

        final boolean isCloseToSquareDisplay = isCloseToSquareDisplay();
        final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
        for (final int rotation : rotations) {
            separateTestJournal();
            rotationSession.set(rotation);
            final int newDeviceRotation = getDeviceRotation(displayId);
            if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
                logE("Got an invalid device rotation value. "
                        + "Continuing the test despite of that, but it is likely to fail.");
            }
            final boolean expectConfigChange = task.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
                    && !isCloseToSquareDisplay;
            if (expectConfigChange) {
                assertActivityLifecycle(activityName, false /* relaunch */);
            }
            final SizeInfo rotatedSizes = activitySession.getConfigInfo().sizeInfo;
            assertSizesRotate(prevSizes, rotatedSizes,
                    // Skip orientation checks if we are not in fullscreen mode, or when the display
                    // is close to square because the app config orientation may always be landscape
                    // excluding the system insets.
                    !expectConfigChange /* skipOrientationCheck */);
            prevSizes = rotatedSizes;
        }
    }

    /**
     * Tests when activity moved from fullscreen stack to docked and back. Activity will be
     * relaunched twice and it should have same config as initial one.
     */
    @Test
    public void testSameConfigurationFullSplitFullRelaunch() {
        moveActivityFullSplitFull(true /* relaunch */);
    }

    /**
     * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
     */
    @Test
    public void testSameConfigurationFullSplitFullNoRelaunch() {
        moveActivityFullSplitFull(false /* relaunch */);
    }

    /**
     * Launches activity in fullscreen task, moves to docked task and back to fullscreen task.
     * Asserts that initial and final reported sizes in fullscreen task are the same.
     */
    private void moveActivityFullSplitFull(boolean relaunch) {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        final ComponentName activityName = relaunch ? TEST_ACTIVITY : RESIZEABLE_ACTIVITY;
        // Launch to fullscreen task and record size.
        separateTestJournal();
        launchActivity(activityName, WINDOWING_MODE_FULLSCREEN);
        final SizeInfo initialFullscreenSizes = getLastReportedSizesForActivity(activityName);

        // Move the task to the primary split task.
        separateTestJournal();
        putActivityInPrimarySplit(activityName);
        // Currently launchActivityInPrimarySplit launches the target activity and then move it
        // to split task, so it requires waiting of lifecycle to get the stable initial size.
        if (relaunch) {
            assertActivityLifecycle(activityName, true /* relaunch */);
        } else {
            // The lifecycle callbacks contain the initial launch event so only wait for
            // multi-window mode changed.
            waitForOnMultiWindowModeChanged(activityName);
        }
        final SizeInfo dockedSizes = getLastReportedSizesForActivity(activityName);
        assertSizesAreSane(initialFullscreenSizes, dockedSizes);

        // Restore to fullscreen.
        separateTestJournal();
        mTaskOrganizer.dismissSplitScreen();
        // Home task could be on top since it was the top-most task while in split-screen mode
        // (dock task was minimized), start the activity again to ensure the activity is at
        // foreground.
        launchActivity(activityName, WINDOWING_MODE_FULLSCREEN);
        assertActivityLifecycle(activityName, relaunch);
        final SizeInfo finalFullscreenSizes = getLastReportedSizesForActivity(activityName);

        // After activity configuration was changed twice it must report same size as original one.
        assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
    }

    /**
     * Tests that an activity with the DialogWhenLarge theme can transform properly when in split
     * screen.
     */
    @Test
    public void testDialogWhenLargeSplitSmall() {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        launchActivitiesInSplitScreen(
                getLaunchActivityBuilder().setTargetActivity(DIALOG_WHEN_LARGE_ACTIVITY),
                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
        int displayId = mWmState.getDisplayByActivity(DIALOG_WHEN_LARGE_ACTIVITY);
        final WindowManagerState.DisplayContent display = mWmState.getDisplay(displayId);
        final int density = display.getDpi();
        final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
        final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);

        mTaskOrganizer.setRootPrimaryTaskBounds(new Rect(0, 0, smallWidthPx, smallHeightPx));
        mWmState.waitForValidState(
                new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
                        .setWindowingMode(WINDOWING_MODE_MULTI_WINDOW)
                        .setActivityType(ACTIVITY_TYPE_STANDARD)
                        .build());
    }

    /**
     * Test that device handles consequent requested orientations and displays the activities.
     */
    @Test
    public void testFullscreenAppOrientationRequests() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        separateTestJournal();
        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
        mWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
        SizeInfo reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
        assertEquals("portrait activity should be in portrait",
                ORIENTATION_PORTRAIT, reportedSizes.orientation);
        separateTestJournal();

        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
        mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
        reportedSizes = getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
        assertEquals("landscape activity should be in landscape",
                ORIENTATION_LANDSCAPE, reportedSizes.orientation);
        separateTestJournal();

        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
        mWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
        reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
        assertEquals("portrait activity should be in portrait",
                ORIENTATION_PORTRAIT, reportedSizes.orientation);
    }

    @Test
    public void testNonfullscreenAppOrientationRequests() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        separateTestJournal();
        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        final SizeInfo initialReportedSizes =
                getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
        assertEquals("portrait activity should be in portrait",
                ORIENTATION_PORTRAIT, initialReportedSizes.orientation);
        separateTestJournal();

        launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        assertEquals("Legacy non-fullscreen activity requested landscape orientation",
                SCREEN_ORIENTATION_LANDSCAPE, mWmState.getLastOrientation());

        // TODO(b/36897968): uncomment once we can suppress unsupported configurations
        // final ReportedSizes updatedReportedSizes =
        //      getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
        // assertEquals("portrait activity should not have moved from portrait",
        //         1 /* portrait */, updatedReportedSizes.orientation);
    }

    /**
     * Test that device handles consequent requested orientations and will not report a config
     * change to an invisible activity.
     */
    @Test
    public void testAppOrientationRequestConfigChanges() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        separateTestJournal();
        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);

        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
                1 /* create */, 1 /* start */, 1 /* resume */,
                0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */);

        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);

        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
                1 /* create */, 1 /* start */, 1 /* resume */,
                1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
        assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY,
                1 /* create */, 1 /* start */, 1 /* resume */,
                0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */);

        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);

        assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
                2 /* create */, 2 /* start */, 2 /* resume */,
                1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
        assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY,
                1 /* create */, 1 /* start */, 1 /* resume */,
                1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
    }

    /**
     * Test that device orientation is restored when an activity that requests it is no longer
     * visible.
     *
     * TODO(b/139936670, b/112688380): This test case fails on some vendor devices which has
     * rotation sensing optimization. So this is listed in cts-known-failures.xml.
     */
    @Test
    public void testAppOrientationRequestConfigClears() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        separateTestJournal();
        launchActivity(TEST_ACTIVITY);
        mWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
        final SizeInfo initialReportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
        final int initialOrientation = initialReportedSizes.orientation;

        // Launch an activity that requests different orientation and check that it will be applied
        final boolean launchingPortrait;
        if (initialOrientation == ORIENTATION_LANDSCAPE) {
            launchingPortrait = true;
        } else if (initialOrientation == ORIENTATION_PORTRAIT) {
            launchingPortrait = false;
        } else {
            fail("Unexpected orientation value: " + initialOrientation);
            return;
        }
        final ComponentName differentOrientationActivity = launchingPortrait
                ? PORTRAIT_ORIENTATION_ACTIVITY : LANDSCAPE_ORIENTATION_ACTIVITY;
        separateTestJournal();
        launchActivity(differentOrientationActivity);
        mWmState.assertVisibility(differentOrientationActivity, true /* visible */);
        final SizeInfo rotatedReportedSizes =
                getLastReportedSizesForActivity(differentOrientationActivity);
        assertEquals("Applied orientation must correspond to activity request",
                launchingPortrait ? 1 : 2, rotatedReportedSizes.orientation);

        // Launch another activity on top and check that its orientation is not affected by previous
        // activity.
        separateTestJournal();
        launchActivity(RESIZEABLE_ACTIVITY);
        mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
        final SizeInfo finalReportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
        assertEquals("Applied orientation must not be influenced by previously visible activity",
                initialOrientation, finalReportedSizes.orientation);
    }

    @Test
    public void testRotatedInfoWithFixedRotationTransform() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        // Start a portrait activity first to ensure that the orientation will change.
        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);

        getLaunchActivityBuilder()
                .setUseInstrumentation()
                .setTargetActivity(LANDSCAPE_ORIENTATION_ACTIVITY)
                // Request the info from onCreate because at that moment the real display hasn't
                // rotated but the activity is rotated.
                .setIntentExtra(bundle -> bundle.putBoolean(EXTRA_CONFIG_INFO_IN_ON_CREATE, true))
                .execute();
        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_LANDSCAPE);

        final SizeInfo reportedSizes =
                getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
        final Bundle extras = TestJournalContainer.get(LANDSCAPE_ORIENTATION_ACTIVITY).extras;
        final ConfigInfo appConfigInfo = extras.getParcelable(EXTRA_APP_CONFIG_INFO);
        final Point onCreateRealDisplaySize = extras.getParcelable(EXTRA_DISPLAY_REAL_SIZE);
        final ConfigInfo onCreateConfigInfo = extras.getParcelable(EXTRA_CONFIG_INFO_IN_ON_CREATE);
        final SizeInfo onCreateSize = onCreateConfigInfo.sizeInfo;
        final ConfigInfo globalConfigInfo =
                extras.getParcelable(EXTRA_SYSTEM_RESOURCES_CONFIG_INFO);
        final SizeInfo globalSizeInfo = globalConfigInfo.sizeInfo;

        assertEquals("The last reported size should be the same as the one from onCreate",
                reportedSizes, onCreateConfigInfo.sizeInfo);

        final Display display = mDm.getDisplay(Display.DEFAULT_DISPLAY);
        final Point expectedRealDisplaySize = new Point();
        display.getRealSize(expectedRealDisplaySize);

        final int expectedRotation = display.getRotation();
        assertEquals("The activity should get the final display rotation in onCreate",
                expectedRotation, onCreateConfigInfo.rotation);
        assertEquals("The application should get the final display rotation in onCreate",
                expectedRotation, appConfigInfo.rotation);
        assertEquals("The orientation of application must be landscape",
                ORIENTATION_LANDSCAPE, appConfigInfo.sizeInfo.orientation);
        assertEquals("The orientation of system resources must be landscape",
                ORIENTATION_LANDSCAPE, globalSizeInfo.orientation);
        assertEquals("The activity should get the final display size in onCreate",
                expectedRealDisplaySize, onCreateRealDisplaySize);

        final boolean isLandscape = expectedRealDisplaySize.x > expectedRealDisplaySize.y;
        assertEquals("The app size of activity should have the same orientation", isLandscape,
                onCreateSize.displayWidth > onCreateSize.displayHeight);
        assertEquals("The application should get the same orientation", isLandscape,
                appConfigInfo.sizeInfo.displayWidth > appConfigInfo.sizeInfo.displayHeight);
        assertEquals("The app display metrics must be landscape", isLandscape,
                appConfigInfo.sizeInfo.metricsWidth > appConfigInfo.sizeInfo.metricsHeight);

        final DisplayMetrics globalMetrics = Resources.getSystem().getDisplayMetrics();
        assertEquals("The display metrics of system resources must be landscape",
                new Point(globalMetrics.widthPixels, globalMetrics.heightPixels),
                new Point(globalSizeInfo.metricsWidth, globalSizeInfo.metricsHeight));
    }

    @Test
    public void testNonFullscreenActivityPermitted() throws Exception {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        final RotationSession rotationSession = createManagedRotationSession();
        rotationSession.set(ROTATION_0);

        launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
        mWmState.assertResumedActivity(
                "target SDK <= 26 non-fullscreen activity should be allowed to launch",
                SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
        assertEquals("non-fullscreen activity requested landscape orientation",
                SCREEN_ORIENTATION_LANDSCAPE, mWmState.getLastOrientation());
    }

    /**
     * Test that device handles moving between two tasks with different orientations.
     */
    @Test
    public void testTaskCloseRestoreFixedOrientation() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        // Start landscape activity.
        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
        mWmState.waitAndAssertLastOrientation("Fullscreen app requested landscape orientation",
                SCREEN_ORIENTATION_LANDSCAPE);

        // Start another activity in a different task.
        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);

        // Request portrait
        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
        waitForBroadcastActivityReady(ORIENTATION_PORTRAIT);

        // Finish activity
        mBroadcastActionTrigger.finishBroadcastReceiverActivity();

        // Verify that activity brought to front is in originally requested orientation.
        mWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY);
        mWmState.waitAndAssertLastOrientation("Should return to app in landscape orientation",
                SCREEN_ORIENTATION_LANDSCAPE);
    }

    /**
     * Test that device handles moving between two tasks with different orientations.
     *
     * TODO(b/139936670, b/112688380): This test case fails on some vendor devices which has
     * rotation sensing optimization. So this is listed in cts-known-failures.xml.
     */
    @Test
    public void testTaskCloseRestoreFreeOrientation() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        // Start landscape activity.
        launchActivity(RESIZEABLE_ACTIVITY);
        mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
        final int initialServerOrientation = mWmState.getLastOrientation();

        // Verify fixed-landscape
        separateTestJournal();
        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_LANDSCAPE);
        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_LANDSCAPE);
        waitForBroadcastActivityReady(ORIENTATION_LANDSCAPE);
        mBroadcastActionTrigger.finishBroadcastReceiverActivity();

        // Verify that activity brought to front is in originally requested orientation.
        mWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
        mWmState.waitAndAssertLastOrientation("Should come back in original server orientation",
                initialServerOrientation);
        assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
                0 /* numConfigChange */);

        // Verify fixed-portrait
        separateTestJournal();
        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
        waitForBroadcastActivityReady(ORIENTATION_PORTRAIT);
        mBroadcastActionTrigger.finishBroadcastReceiverActivity();

        // Verify that activity brought to front is in originally requested orientation.
        mWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
        mWmState.waitAndAssertLastOrientation("Should come back in original server orientation",
                initialServerOrientation);
        assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
                0 /* numConfigChange */);
    }

    /**
     * Test that activity orientation will change when device is rotated.
     * Also verify that occluded activity will not get config changes.
     */
    @Test
    public void testAppOrientationWhenRotating() throws Exception {
        assumeTrue("Skipping test: no rotation support", supportsRotation());

        // Start resizeable activity that handles configuration changes.
        separateTestJournal();
        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);

        final int displayId = mWmState.getDisplayByActivity(RESIZEABLE_ACTIVITY);

        // Rotate the activity and check that it receives configuration changes with a different
        // orientation each time.
        final RotationSession rotationSession = createManagedRotationSession();
        assumeTrue("Skipping test: no locked user rotation mode support.",
                supportsLockedUserRotation(rotationSession, displayId));

        rotationSession.set(ROTATION_0);
        SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
        int prevOrientation = reportedSizes.orientation;

        final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
        for (final int rotation : rotations) {
            separateTestJournal();
            rotationSession.set(rotation);

            // Verify lifecycle count and orientation changes.
            assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
                    1 /* numConfigChange */);
            reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
            assertNotEquals(prevOrientation, reportedSizes.orientation);
            assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */,
                    0 /* numConfigChange */);

            prevOrientation = reportedSizes.orientation;
        }
    }

    /**
     * Test that the orientation for a simulated display context derived from an application context
     * will not change when the device rotates.
     */
    @Test
    public void testAppContextDerivedDisplayContextOrientationWhenRotating() {
        assumeTrue("Skipping test: no rotation support", supportsRotation());
        assumeTrue("Skipping test: no multi-display support", supportsMultiDisplay());

        assertDisplayContextDoesntChangeOrientationWhenRotating(Activity::getApplicationContext);
    }

    /**
     * Test that the orientation for a simulated display context derived from an activity context
     * will not change when the device rotates.
     */
    @Test
    public void testActivityContextDerivedDisplayContextOrientationWhenRotating() {
        assumeTrue("Skipping test: no rotation support", supportsRotation());
        assumeTrue("Skipping test: no multi-display support", supportsMultiDisplay());

        assertDisplayContextDoesntChangeOrientationWhenRotating(activity -> activity);
    }

    /**
     * Asserts that the orientation for a simulated display context derived from a base context will
     * not change when the device rotates.
     *
     * @param baseContextSupplier function that returns a base context used to created the display
     *                            context.
     *
     * @see #testAppContextDerivedDisplayContextOrientationWhenRotating
     * @see #testActivityContextDerivedDisplayContextOrientationWhenRotating
     */
    private void assertDisplayContextDoesntChangeOrientationWhenRotating(
            Function<Activity, Context> baseContextSupplier) {
        RotationSession rotationSession = createManagedRotationSession();
        rotationSession.set(ROTATION_0);

        TestActivitySession<ConfigChangeHandlingActivity> activitySession
                = createManagedTestActivitySession();
        activitySession.launchTestActivityOnDisplaySync(
                ConfigChangeHandlingActivity.class,
                Display.DEFAULT_DISPLAY,
                WINDOWING_MODE_FULLSCREEN);
        final ConfigChangeHandlingActivity activity = activitySession.getActivity();

        VirtualDisplaySession virtualDisplaySession = createManagedVirtualDisplaySession();
        WindowManagerState.DisplayContent displayContent = virtualDisplaySession
                .setSimulateDisplay(true)
                .setSimulationDisplaySize(100 /* width */, 200 /* height */)
                .createDisplay();

        DisplayManager dm = activity.getSystemService(DisplayManager.class);
        Display simulatedDisplay = dm.getDisplay(displayContent.mId);
        Context simulatedDisplayContext = baseContextSupplier.apply(activity)
                .createDisplayContext(simulatedDisplay);
        assertEquals(ORIENTATION_PORTRAIT,
                simulatedDisplayContext.getResources().getConfiguration().orientation);

        separateTestJournal();

        final int[] rotations = {ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0};
        for (final int rotation : rotations) {
            rotationSession.set(rotation);

            assertRelaunchOrConfigChanged(activity.getComponentName(), 0 /* numRelaunch */,
                    1 /* numConfigChange */);
            separateTestJournal();

            assertEquals("Display context orientation must not be changed", ORIENTATION_PORTRAIT,
                    simulatedDisplayContext.getResources().getConfiguration().orientation);
        }
    }

    /**
     * Test that activity orientation will not change when trying to rotate fixed-orientation
     * activity.
     * Also verify that occluded activity will not get config changes.
     */
    @Test
    public void testFixedOrientationWhenRotating() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());
        // TODO(b/110533226): Fix test on devices with display cutout
        assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
                hasDisplayCutout());

        // Start portrait-fixed activity
        separateTestJournal();
        final ActivitySession activitySession = createManagedActivityClientSession()
                .startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
                        .setTargetActivity(RESIZEABLE_ACTIVITY));
        mWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);

        final int displayId = mWmState.getDisplayByActivity(RESIZEABLE_ACTIVITY);

        final RotationSession rotationSession = createManagedRotationSession();
        assumeTrue("Skipping test: no user locked rotation support.",
                supportsLockedUserRotation(rotationSession, displayId));

        launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
        final SizeInfo initialSize = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);

        // Rotate the display and check that the orientation doesn't change
        rotationSession.set(ROTATION_0);
        final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
        for (final int rotation : rotations) {
            separateTestJournal();
            rotationSession.set(rotation, false /* waitDeviceRotation */);

            // Verify lifecycle count and orientation changes.
            assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */,
                    0 /* numConfigChange */);
            final SizeInfo currentSize = activitySession.getConfigInfo().sizeInfo;
            assertEquals("Sizes must not be changed", initialSize, currentSize);
            assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
                    0 /* numConfigChange */);
        }
    }

    /**
     * Test that device handles moving between two tasks with different orientations.
     */
    @Test
    public void testTaskMoveToBackOrientation() {
        assumeTrue("Skipping test: no orientation request support", supportsOrientationRequest());

        // Start landscape activity.
        launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
        mWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
        mWmState.waitAndAssertLastOrientation("Fullscreen app requested landscape orientation",
                SCREEN_ORIENTATION_LANDSCAPE);

        // Start another activity in a different task.
        launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);

        // Request portrait
        mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
        mWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
        waitForBroadcastActivityReady(ORIENTATION_PORTRAIT);

        // Finish activity
        mBroadcastActionTrigger.moveTopTaskToBack();

        // Verify that activity brought to front is in originally requested orientation.
        mWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
        mWmState.waitAndAssertLastOrientation("Should return to app in landscape orientation",
                SCREEN_ORIENTATION_LANDSCAPE);
    }

    /**
     * Test that device doesn't change device orientation by app request while in multi-window.
     */
    @Test
    public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
        assumeTrue("Skipping test: no rotation support", supportsRotation());
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        requestOrientationInSplitScreen(createManagedRotationSession(),
                ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY);
    }

    /**
     * Test that device doesn't change device orientation by app request while in multi-window.
     */
    @Test
    public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        requestOrientationInSplitScreen(createManagedRotationSession(),
                ROTATION_0 /* landscape */, PORTRAIT_ORIENTATION_ACTIVITY);
    }

    /**
     * Rotate the device and launch specified activity in split-screen, checking if orientation
     * didn't change.
     */
    private void requestOrientationInSplitScreen(RotationSession rotationSession, int orientation,
            ComponentName activity) throws Exception {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        // Set initial orientation.
        rotationSession.set(orientation);

        // Launch activities that request orientations and check that device doesn't rotate.
        launchActivitiesInSplitScreen(
                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
                getLaunchActivityBuilder().setTargetActivity(activity).setMultipleTask(true));

        mWmState.assertVisibility(activity, true /* visible */);
        assertEquals("Split-screen apps shouldn't influence device orientation",
                orientation, mWmState.getRotation());

        getLaunchActivityBuilder().setMultipleTask(true).setTargetActivity(activity).execute();
        mWmState.computeState(activity);
        mWmState.assertVisibility(activity, true /* visible */);
        assertEquals("Split-screen apps shouldn't influence device orientation",
                orientation, mWmState.getRotation());
    }

    /**
     * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
     * have flipped.
     */
    private static void assertSizesRotate(SizeInfo rotationA, SizeInfo rotationB,
            boolean skipOrientationCheck) {
        assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
        assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
        assertEquals(rotationB.displayWidth, rotationB.metricsWidth);
        assertEquals(rotationB.displayHeight, rotationB.metricsHeight);

        if (skipOrientationCheck) {
            // All done if we are not doing orientation check.
            return;
        }
        final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight;
        final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight;
        assertFalse(beforePortrait == afterPortrait);

        final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp;
        final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp;
        assertEquals(beforePortrait, beforeConfigPortrait);
        assertEquals(afterPortrait, afterConfigPortrait);

        assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp);
    }

    /**
     * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
     * that are smaller than the dockedSizes.
     */
    private static void assertSizesAreSane(SizeInfo fullscreenSizes, SizeInfo dockedSizes) {
        final boolean isHorizontalDivision =
                fullscreenSizes.displayHeight - dockedSizes.displayHeight >
                fullscreenSizes.displayWidth - dockedSizes.displayWidth;
        if (isHorizontalDivision) {
            assertThat(dockedSizes.displayHeight, lessThan(fullscreenSizes.displayHeight));
            assertThat(dockedSizes.heightDp, lessThan(fullscreenSizes.heightDp));
            assertThat(dockedSizes.metricsHeight, lessThan(fullscreenSizes.metricsHeight));
        } else {
            assertThat(dockedSizes.displayWidth, lessThan(fullscreenSizes.displayWidth));
            assertThat(dockedSizes.widthDp, lessThan(fullscreenSizes.widthDp));
            assertThat(dockedSizes.metricsWidth, lessThan(fullscreenSizes.metricsWidth));
        }
    }

    /**
     * Throws an AssertionError if sizes are different.
     */
    private static void assertSizesAreSame(SizeInfo firstSize, SizeInfo secondSize) {
        assertEquals(firstSize.widthDp, secondSize.widthDp);
        assertEquals(firstSize.heightDp, secondSize.heightDp);
        assertEquals(firstSize.displayWidth, secondSize.displayWidth);
        assertEquals(firstSize.displayHeight, secondSize.displayHeight);
        assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
        assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
        assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
    }

    private void waitForBroadcastActivityReady(int orientation) {
        mWmState.waitForActivityOrientation(BROADCAST_RECEIVER_ACTIVITY, orientation);
        mWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
    }

    /**
     * Test launching an activity which requests specific UI mode during creation.
     */
    @Test
    public void testLaunchWithUiModeChange() {
        // Launch activity that changes UI mode and handles this configuration change.
        launchActivity(NIGHT_MODE_ACTIVITY);
        mWmState.waitForActivityState(NIGHT_MODE_ACTIVITY, STATE_RESUMED);

        // Check if activity is launched successfully.
        mWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
        mWmState.assertFocusedActivity("Launched activity should be focused",
                NIGHT_MODE_ACTIVITY);
        mWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY);
    }

    @Test
    public void testAppConfigurationMatchesActivityInMultiWindow() throws Exception {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        final ActivitySession activitySession = createManagedActivityClientSession()
                .startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setTargetActivity(RESIZEABLE_ACTIVITY));
        putActivityInPrimarySplit(RESIZEABLE_ACTIVITY);
        SizeInfo dockedActivitySizes = getActivitySizeInfo(activitySession);
        SizeInfo applicationSizes = getAppSizeInfo(activitySession);
        assertSizesAreSame(dockedActivitySizes, applicationSizes);

        // Move the activity to fullscreen and check that the size was updated
        separateTestJournal();
        mTaskOrganizer.dismissSplitScreen(true /* primaryOnTop */);
        waitForOrFail("Activity and application configuration must match",
                () -> activityAndAppSizesMatch(activitySession));
        final SizeInfo fullscreenSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
        applicationSizes = getAppSizeInfo(activitySession);
        assertSizesAreSane(fullscreenSizes, dockedActivitySizes);
        assertSizesAreSame(fullscreenSizes, applicationSizes);

        // Move the activity to docked size again, check if the sizes were updated
        separateTestJournal();
        putActivityInPrimarySplit(RESIZEABLE_ACTIVITY);
        waitForOrFail("Activity and application configuration must match",
                () -> activityAndAppSizesMatch(activitySession));
        dockedActivitySizes = getActivitySizeInfo(activitySession);
        applicationSizes = getAppSizeInfo(activitySession);
        assertSizesAreSane(fullscreenSizes, dockedActivitySizes);
        assertSizesAreSame(dockedActivitySizes, applicationSizes);
    }

    @Test
    public void testAppConfigurationMatchesTopActivityInMultiWindow() throws Exception {
        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());

        // Launch initial activity in fullscreen and assert sizes
        final ActivitySession fullscreenActivitySession = createManagedActivityClientSession()
                .startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setTargetActivity(TEST_ACTIVITY)
                        .setWindowingMode(WINDOWING_MODE_FULLSCREEN));
        SizeInfo fullscreenActivitySizes = getActivitySizeInfo(fullscreenActivitySession);
        SizeInfo applicationSizes = getAppSizeInfo(fullscreenActivitySession);
        assertSizesAreSame(fullscreenActivitySizes, applicationSizes);

        // Launch second activity in split-screen and assert that sizes were updated
        separateTestJournal();
        final ActivitySession secondActivitySession = createManagedActivityClientSession()
                .startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setTargetActivity(RESIZEABLE_ACTIVITY)
                        .setNewTask(true)
                        .setMultipleTask(true));
        putActivityInPrimarySplit(RESIZEABLE_ACTIVITY);
        waitForOrFail("Activity and application configuration must match",
                () -> activityAndAppSizesMatch(secondActivitySession));
        SizeInfo dockedActivitySizes = getActivitySizeInfo(secondActivitySession);
        applicationSizes = getAppSizeInfo(secondActivitySession);
        assertSizesAreSame(dockedActivitySizes, applicationSizes);
        assertSizesAreSane(fullscreenActivitySizes, dockedActivitySizes);

        // Launch third activity in secondary split-screen and assert that sizes were updated
        separateTestJournal();
        final ActivitySession thirdActivitySession = createManagedActivityClientSession()
                .startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setTargetActivity(RESIZEABLE_ACTIVITY)
                        .setNewTask(true)
                        .setMultipleTask(true));
        putActivityInPrimarySplit(RESIZEABLE_ACTIVITY);
        waitForOrFail("Activity and application configuration must match",
                () -> activityAndAppSizesMatch(thirdActivitySession));
        SizeInfo secondarySplitActivitySizes = getActivitySizeInfo(thirdActivitySession);
        applicationSizes = getAppSizeInfo(thirdActivitySession);
        assertSizesAreSame(secondarySplitActivitySizes, applicationSizes);
        assertSizesAreSane(fullscreenActivitySizes, secondarySplitActivitySizes);
    }

    @Test
    public void testAppConfigurationMatchesActivityInFreeform() throws Exception {
        assumeTrue("Skipping test: no freeform support", supportsFreeform());

        // Launch activity in freeform and assert sizes
        final ActivitySession freeformActivitySession = createManagedActivityClientSession()
                .startActivity(getLaunchActivityBuilder()
                        .setUseInstrumentation()
                        .setTargetActivity(TEST_ACTIVITY)
                        .setWindowingMode(WINDOWING_MODE_FREEFORM));
        SizeInfo freeformActivitySizes = getActivitySizeInfo(freeformActivitySession);
        SizeInfo applicationSizes = getAppSizeInfo(freeformActivitySession);
        assertSizesAreSame(freeformActivitySizes, applicationSizes);
    }

    private boolean activityAndAppSizesMatch(ActivitySession activitySession) {
        final SizeInfo activitySize = activitySession.getConfigInfo().sizeInfo;
        final SizeInfo appSize = activitySession.getAppConfigInfo().sizeInfo;
        return activitySize.equals(appSize);
    }

    private SizeInfo getActivitySizeInfo(ActivitySession activitySession) {
        return activitySession.getConfigInfo().sizeInfo;
    }

    private SizeInfo getAppSizeInfo(ActivitySession activitySession) {
        return activitySession.getAppConfigInfo().sizeInfo;
    }
}
