blob: 4f3cdff57b12b07c34cad20e276b866f3cf57cac [file] [log] [blame]
/*
* 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.am;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.server.am.ActivityAndWindowManagersState.dpToPx;
import static android.server.am.ActivityManagerState.STATE_RESUMED;
import static android.server.am.ComponentNameUtils.getWindowName;
import static android.server.am.Components.BROADCAST_RECEIVER_ACTIVITY;
import static android.server.am.Components.BroadcastReceiverActivity.ACTION_TRIGGER_BROADCAST;
import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_BROADCAST_ORIENTATION;
import static android.server.am.Components.BroadcastReceiverActivity.EXTRA_MOVE_BROADCAST_TO_BACK;
import static android.server.am.Components.DIALOG_WHEN_LARGE_ACTIVITY;
import static android.server.am.Components.LANDSCAPE_ORIENTATION_ACTIVITY;
import static android.server.am.Components.LAUNCHING_ACTIVITY;
import static android.server.am.Components.NIGHT_MODE_ACTIVITY;
import static android.server.am.Components.PORTRAIT_ORIENTATION_ACTIVITY;
import static android.server.am.Components.RESIZEABLE_ACTIVITY;
import static android.server.am.Components.TEST_ACTIVITY;
import static android.server.am.StateLogger.log;
import static android.server.am.StateLogger.logE;
import static android.server.am.translucentapp.Components.TRANSLUCENT_LANDSCAPE_ACTIVITY;
import static android.server.am.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.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static com.android.compatibility.common.util.PackageUtil.supportsRotation;
import android.content.ComponentName;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.FlakyTest;
import org.junit.Ignore;
import org.junit.Test;
import java.util.List;
/**
* Build/Install/Run:
* atest CtsActivityManagerDeviceTestCases:ActivityManagerAppConfigurationTests
*/
public class ActivityManagerAppConfigurationTests extends ActivityManagerTestBase {
// TODO(b/70247058): Use {@link Context#sendBroadcast(Intent).
// Shell command to move {@link #BROADCAST_RECEIVER_ACTIVITY} task to back.
private static final String MOVE_TASK_TO_BACK_BROADCAST = "am broadcast -a "
+ ACTION_TRIGGER_BROADCAST + " --ez " + EXTRA_MOVE_BROADCAST_TO_BACK + " true";
// Shell command to request portrait orientation to {@link #BROADCAST_RECEIVER_ACTIVITY}.
private static final String REQUEST_PORTRAIT_BROADCAST = "am broadcast -a "
+ ACTION_TRIGGER_BROADCAST + " --ei " + EXTRA_BROADCAST_ORIENTATION + " 1";
private static final String REQUEST_LANDSCAPE_BROADCAST = "am broadcast -a "
+ ACTION_TRIGGER_BROADCAST + " --ei " + EXTRA_BROADCAST_ORIENTATION + " 0";
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());
LogSeparator logSeparator = separateLogs();
launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
logSeparator = separateLogs();
setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
assertSizesAreSane(fullscreenSizes, dockedSizes);
}
/**
* Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
* from docked state to fullscreen (reverse).
*/
@Presubmit
@Test
@FlakyTest(bugId = 71792393)
public void testConfigurationUpdatesWhenResizedFromDockedStack() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
LogSeparator logSeparator = separateLogs();
launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
logSeparator = separateLogs();
setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
assertSizesAreSane(fullscreenSizes, dockedSizes);
}
/**
* Tests whether the Display sizes change when rotating the device.
*/
@Test
public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
try (final RotationSession rotationSession = new RotationSession()) {
rotationSession.set(ROTATION_0);
final LogSeparator logSeparator = separateLogs();
launchActivity(RESIZEABLE_ACTIVITY,
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
rotateAndCheckSizes(rotationSession, initialSizes);
}
}
/**
* Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
* is in the docked stack.
*/
@Presubmit
@Test
public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
try (final RotationSession rotationSession = new RotationSession()) {
rotationSession.set(ROTATION_0);
final LogSeparator logSeparator = separateLogs();
// 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).execute();
final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
rotateAndCheckSizes(rotationSession, initialSizes);
}
}
/**
* Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
* is launched to side from docked stack.
*/
@Presubmit
@Test
public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
try (final RotationSession rotationSession = new RotationSession()) {
rotationSession.set(ROTATION_0);
final LogSeparator logSeparator = separateLogs();
launchActivitiesInSplitScreen(
getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
rotateAndCheckSizes(rotationSession, initialSizes);
}
}
private void rotateAndCheckSizes(RotationSession rotationSession, ReportedSizes prevSizes)
throws Exception {
final ActivityManagerState.ActivityTask task =
mAmWmState.getAmState().getTaskByActivity(RESIZEABLE_ACTIVITY);
final int displayId = mAmWmState.getAmState().getStackById(task.mStackId).mDisplayId;
assumeTrue(supportsLockedUserRotation(rotationSession, displayId));
final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
for (final int rotation : rotations) {
final LogSeparator logSeparator = separateLogs();
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 ReportedSizes rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY,
logSeparator);
assertSizesRotate(prevSizes, rotatedSizes,
// Skip orientation checks if we are not in fullscreen mode.
task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN);
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(TEST_ACTIVITY);
}
/**
* Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
*/
@Presubmit
@Test
public void testSameConfigurationFullSplitFullNoRelaunch() {
moveActivityFullSplitFull(RESIZEABLE_ACTIVITY);
}
/**
* Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
* Last operation is done in a way which simulates split-screen divider movement maximizing
* docked stack size and then moving task to fullscreen stack - the same way it is done when
* user long-presses overview/recents button to exit split-screen.
* Asserts that initial and final reported sizes in fullscreen stack are the same.
*/
private void moveActivityFullSplitFull(ComponentName activityName) {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
// Launch to fullscreen stack and record size.
LogSeparator logSeparator = separateLogs();
launchActivity(activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
final ReportedSizes initialFullscreenSizes = getActivityDisplaySize(activityName,
logSeparator);
final Rect displayRect = getDisplayRect(activityName);
// Move to docked stack.
logSeparator = separateLogs();
setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final ReportedSizes dockedSizes = getActivityDisplaySize(activityName, logSeparator);
assertSizesAreSane(initialFullscreenSizes, dockedSizes);
// Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
// will come up.
launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
mAmWmState.computeState(false /* compareTaskAndStackBounds */,
new WaitForValidActivityState.Builder(activityName).build());
final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
.getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
// Resize docked stack to fullscreen size. This will trigger activity relaunch with
// non-empty override configuration corresponding to fullscreen size.
logSeparator = separateLogs();
mAm.resizeStack(stack.mStackId, displayRect);
// Move activity back to fullscreen stack.
setActivityTaskWindowingMode(activityName,
WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
final ReportedSizes finalFullscreenSizes = getActivityDisplaySize(activityName,
logSeparator);
// After activity configuration was changed twice it must report same size as original one.
assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
}
/**
* Tests when activity moved from docked stack to fullscreen and back. Activity will be
* relaunched twice and it should have same config as initial one.
*/
@Test
public void testSameConfigurationSplitFullSplitRelaunch() {
moveActivitySplitFullSplit(TEST_ACTIVITY);
}
/**
* Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
*/
@Test
public void testSameConfigurationSplitFullSplitNoRelaunch() {
moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY);
}
/**
* Tests that an activity with the DialogWhenLarge theme can transform properly when in split
* screen.
*/
@Presubmit
@Test
public void testDialogWhenLargeSplitSmall() {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
launchActivity(DIALOG_WHEN_LARGE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
.getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final WindowManagerState.Display display =
mAmWmState.getWmState().getDisplay(stack.mDisplayId);
final int density = display.getDpi();
final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
mAm.resizeStack(stack.mStackId, new Rect(0, 0, smallWidthPx, smallHeightPx));
mAmWmState.waitForValidState(
new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
.setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
.setActivityType(ACTIVITY_TYPE_STANDARD)
.build());
}
/**
* Test that device handles consequent requested orientations and displays the activities.
*/
@Presubmit
@Test
@FlakyTest(bugId = 71875755)
public void testFullscreenAppOrientationRequests() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
LogSeparator logSeparator = separateLogs();
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
ReportedSizes reportedSizes =
getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
assertEquals("portrait activity should be in portrait",
1 /* portrait */, reportedSizes.orientation);
logSeparator = separateLogs();
launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
reportedSizes =
getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator);
assertEquals("landscape activity should be in landscape",
2 /* landscape */, reportedSizes.orientation);
logSeparator = separateLogs();
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
reportedSizes =
getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
assertEquals("portrait activity should be in portrait",
1 /* portrait */, reportedSizes.orientation);
logSeparator = separateLogs();
}
@Test
public void testNonfullscreenAppOrientationRequests() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
LogSeparator logSeparator = separateLogs();
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
final ReportedSizes initialReportedSizes =
getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
assertEquals("portrait activity should be in portrait",
1 /* portrait */, initialReportedSizes.orientation);
logSeparator = separateLogs();
launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
assertEquals("Legacy non-fullscreen activity requested landscape orientation",
0 /* landscape */, mAmWmState.getWmState().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 rotation support", supportsRotation());
LogSeparator logSeparator = separateLogs();
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
1 /* start */, 1 /* resume */, 0 /* pause */, 0 /* stop */, 0 /* destroy */,
0 /* config */);
launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
1 /* start */, 1 /* resume */, 1 /* pause */, 1 /* stop */, 0 /* destroy */,
0 /* config */);
assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator, 1 /* create */,
1 /* start */, 1 /* resume */, 0 /* pause */, 0 /* stop */, 0 /* destroy */,
0 /* config */);
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY, logSeparator, 2 /* create */,
2 /* start */, 2 /* resume */, 1 /* pause */, 1 /* stop */, 0 /* destroy */,
0 /* config */);
assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY, logSeparator, 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.
*/
@Test
public void testAppOrientationRequestConfigClears() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
LogSeparator logSeparator = separateLogs();
launchActivity(TEST_ACTIVITY);
mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
final ReportedSizes initialReportedSizes =
getLastReportedSizesForActivity(TEST_ACTIVITY, logSeparator);
final int initialOrientation = initialReportedSizes.orientation;
// Launch an activity that requests different orientation and check that it will be applied
final boolean launchingPortrait;
if (initialOrientation == 2 /* landscape */) {
launchingPortrait = true;
} else if (initialOrientation == 1 /* portrait */) {
launchingPortrait = false;
} else {
fail("Unexpected orientation value: " + initialOrientation);
return;
}
final ComponentName differentOrientationActivity = launchingPortrait
? PORTRAIT_ORIENTATION_ACTIVITY : LANDSCAPE_ORIENTATION_ACTIVITY;
logSeparator = separateLogs();
launchActivity(differentOrientationActivity);
mAmWmState.assertVisibility(differentOrientationActivity, true /* visible */);
final ReportedSizes rotatedReportedSizes =
getLastReportedSizesForActivity(differentOrientationActivity, logSeparator);
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.
logSeparator = separateLogs();
launchActivity(RESIZEABLE_ACTIVITY);
mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
final ReportedSizes finalReportedSizes =
getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
assertEquals("Applied orientation must not be influenced by previously visible activity",
initialOrientation, finalReportedSizes.orientation);
}
// TODO(b/70870253): This test seems malfunction.
@Ignore("b/70870253")
@Test
public void testNonFullscreenActivityProhibited() {
// We do not wait for the activity as it should not launch based on the restrictions around
// specifying orientation. We instead start an activity known to launch immediately after
// so that we can ensure processing the first activity occurred.
launchActivityNoWait(TRANSLUCENT_LANDSCAPE_ACTIVITY);
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
mAmWmState.getAmState().containsActivity(TRANSLUCENT_LANDSCAPE_ACTIVITY));
}
@Test
public void testNonFullscreenActivityPermitted() throws Exception {
if(!supportsRotation()) {
//cannot physically rotate the screen on automotive device, skip
return;
}
try (final RotationSession rotationSession = new RotationSession()) {
rotationSession.set(ROTATION_0);
launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
mAmWmState.assertResumedActivity(
"target SDK <= 26 non-fullscreen activity should be allowed to launch",
SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
assertEquals("non-fullscreen activity requested landscape orientation",
0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
}
}
/**
* Test that device handles moving between two tasks with different orientations.
*/
@Test
public void testTaskCloseRestoreFixedOrientation() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
// Start landscape activity.
launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
assertEquals("Fullscreen app requested landscape orientation",
0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
// Start another activity in a different task.
launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
// Request portrait
executeShellCommand(REQUEST_PORTRAIT_BROADCAST);
mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
waitForBroadcastActivityReady(SCREEN_ORIENTATION_PORTRAIT);
// Finish activity
executeShellCommand(FINISH_ACTIVITY_BROADCAST);
// Verify that activity brought to front is in originally requested orientation.
mAmWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY);
assertEquals("Should return to app in landscape orientation",
0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
}
/**
* Test that device handles moving between two tasks with different orientations.
*/
@Test
public void testTaskCloseRestoreFreeOrientation() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
// Start landscape activity.
launchActivity(RESIZEABLE_ACTIVITY);
mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
final int initialServerOrientation = mAmWmState.getWmState().getLastOrientation();
// Verify fixed-landscape
LogSeparator logSeparator = separateLogs();
launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
executeShellCommand(REQUEST_LANDSCAPE_BROADCAST);
mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_LANDSCAPE);
waitForBroadcastActivityReady(SCREEN_ORIENTATION_LANDSCAPE);
executeShellCommand(FINISH_ACTIVITY_BROADCAST);
// Verify that activity brought to front is in originally requested orientation.
mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
ReportedSizes reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY,
logSeparator);
assertNull("Should come back in original orientation", reportedSizes);
assertEquals("Should come back in original server orientation",
initialServerOrientation, mAmWmState.getWmState().getLastOrientation());
assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
0 /* numConfigChange */, logSeparator);
// Verify fixed-portrait
logSeparator = separateLogs();
launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
executeShellCommand(REQUEST_PORTRAIT_BROADCAST);
mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
waitForBroadcastActivityReady(SCREEN_ORIENTATION_PORTRAIT);
executeShellCommand(FINISH_ACTIVITY_BROADCAST);
// Verify that activity brought to front is in originally requested orientation.
mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
assertNull("Should come back in original orientation", reportedSizes);
assertEquals("Should come back in original server orientation",
initialServerOrientation, mAmWmState.getWmState().getLastOrientation());
assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
0 /* numConfigChange */, logSeparator);
}
/**
* 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.
LogSeparator logSeparator = separateLogs();
launchActivity(TEST_ACTIVITY);
launchActivity(RESIZEABLE_ACTIVITY);
mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
final int displayId = mAmWmState.getAmState().getDisplayByActivity(RESIZEABLE_ACTIVITY);
// Rotate the activity and check that it receives configuration changes with a different
// orientation each time.
try (final RotationSession rotationSession = new RotationSession()) {
assumeTrue("Skipping test: no locked user rotation mode support.",
supportsLockedUserRotation(rotationSession, displayId));
rotationSession.set(ROTATION_0);
ReportedSizes reportedSizes =
getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
int prevOrientation = reportedSizes.orientation;
final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
for (final int rotation : rotations) {
logSeparator = separateLogs();
rotationSession.set(rotation);
// Verify lifecycle count and orientation changes.
assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
1 /* numConfigChange */, logSeparator);
reportedSizes =
getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY, logSeparator);
assertNotEquals(prevOrientation, reportedSizes.orientation);
assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */,
0 /* numConfigChange */, logSeparator);
prevOrientation = reportedSizes.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() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
// 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
LogSeparator logSeparator = separateLogs();
launchActivity(RESIZEABLE_ACTIVITY);
launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
final int displayId = mAmWmState.getAmState()
.getDisplayByActivity(PORTRAIT_ORIENTATION_ACTIVITY);
// Rotate the activity and check that the orientation doesn't change
try (final RotationSession rotationSession = new RotationSession()) {
assumeTrue("Skipping test: no user locked rotation support.",
supportsLockedUserRotation(rotationSession, displayId));
rotationSession.set(ROTATION_0);
final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
for (final int rotation : rotations) {
logSeparator = separateLogs();
rotationSession.set(rotation);
// Verify lifecycle count and orientation changes.
assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */,
0 /* numConfigChange */, logSeparator);
final ReportedSizes reportedSizes = getLastReportedSizesForActivity(
PORTRAIT_ORIENTATION_ACTIVITY, logSeparator);
assertNull("No new sizes must be reported", reportedSizes);
assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
0 /* numConfigChange */, logSeparator);
}
}
}
/**
* Test that device handles moving between two tasks with different orientations.
*/
@Presubmit
@Test
@FlakyTest(bugId = 71792393)
public void testTaskMoveToBackOrientation() {
assumeTrue("Skipping test: no rotation support", supportsRotation());
// Start landscape activity.
launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
assertEquals("Fullscreen app requested landscape orientation",
0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
// Start another activity in a different task.
launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
// Request portrait
executeShellCommand(REQUEST_PORTRAIT_BROADCAST);
mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
waitForBroadcastActivityReady(SCREEN_ORIENTATION_PORTRAIT);
// Finish activity
executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
// Verify that activity brought to front is in originally requested orientation.
mAmWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
assertEquals("Should return to app in landscape orientation",
0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
}
/**
* Test that device doesn't change device orientation by app request while in multi-window.
*/
@Presubmit
@FlakyTest(bugId = 71918731)
@Test
public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
assumeTrue("Skipping test: no rotation support", supportsRotation());
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
try (final RotationSession rotationSession = new RotationSession()) {
requestOrientationInSplitScreen(rotationSession,
ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY);
}
}
/**
* Test that device doesn't change device orientation by app request while in multi-window.
*/
@Presubmit
@Test
public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
try (final RotationSession rotationSession = new RotationSession()) {
requestOrientationInSplitScreen(rotationSession,
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));
mAmWmState.assertVisibility(activity, true /* visible */);
assertEquals("Split-screen apps shouldn't influence device orientation",
orientation, mAmWmState.getWmState().getRotation());
getLaunchActivityBuilder().setMultipleTask(true).setTargetActivity(activity).execute();
mAmWmState.computeState(activity);
mAmWmState.assertVisibility(activity, true /* visible */);
assertEquals("Split-screen apps shouldn't influence device orientation",
orientation, mAmWmState.getWmState().getRotation());
}
/**
* Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
* Asserts that initial and final reported sizes in docked stack are the same.
*/
private void moveActivitySplitFullSplit(ComponentName activityName) {
assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
// Launch to docked stack and record size.
LogSeparator logSeparator = separateLogs();
launchActivityInSplitScreenWithRecents(activityName);
final ReportedSizes initialDockedSizes = getActivityDisplaySize(activityName, logSeparator);
// Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
// will come up.
launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
mAmWmState.computeState(false /* compareTaskAndStackBounds */,
new WaitForValidActivityState.Builder(activityName).build());
// Move to fullscreen stack.
logSeparator = separateLogs();
setActivityTaskWindowingMode(
activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
final ReportedSizes fullscreenSizes = getActivityDisplaySize(activityName, logSeparator);
assertSizesAreSane(fullscreenSizes, initialDockedSizes);
// Move activity back to docked stack.
logSeparator = separateLogs();
setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
final ReportedSizes finalDockedSizes = getActivityDisplaySize(activityName, logSeparator);
// After activity configuration was changed twice it must report same size as original one.
assertSizesAreSame(initialDockedSizes, finalDockedSizes);
}
/**
* Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
* have flipped.
*/
private static void assertSizesRotate(ReportedSizes rotationA, ReportedSizes 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(ReportedSizes fullscreenSizes, ReportedSizes dockedSizes)
{
final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
if (portrait) {
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(ReportedSizes firstSize, ReportedSizes 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 ReportedSizes getActivityDisplaySize(ComponentName activityName,
LogSeparator logSeparator) {
mAmWmState.computeState(false /* compareTaskAndStackBounds */,
new WaitForValidActivityState(activityName));
final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
assertNotNull(details);
return details;
}
private Rect getDisplayRect(ComponentName activityName) {
final String windowName = getWindowName(activityName);
mAmWmState.computeState(activityName);
mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
final List<WindowManagerState.WindowState> windowList =
mAmWmState.getWmState().getMatchingVisibleWindowState(windowName);
assertEquals("Should have exactly one window state for the activity.", 1,
windowList.size());
WindowManagerState.WindowState windowState = windowList.get(0);
assertNotNull("Should have a valid window", windowState);
WindowManagerState.Display display = mAmWmState.getWmState()
.getDisplay(windowState.getDisplayId());
assertNotNull("Should be on a display", display);
return display.getDisplayRect();
}
private void waitForBroadcastActivityReady(int orientation) {
mAmWmState.waitForActivityOrientation(BROADCAST_RECEIVER_ACTIVITY, orientation);
mAmWmState.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);
mAmWmState.waitForActivityState(NIGHT_MODE_ACTIVITY, STATE_RESUMED);
// Check if activity is launched successfully.
mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
mAmWmState.assertFocusedActivity("Launched activity should be focused",
NIGHT_MODE_ACTIVITY);
mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY);
}
}