/* * 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 baseContextSupplier) { RotationSession rotationSession = createManagedRotationSession(); rotationSession.set(ROTATION_0); TestActivitySession 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; } }