• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */
16 
17 package android.server.wm;
18 
19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
21 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
22 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
23 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
24 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
25 import static android.server.wm.ActivityAndWindowManagersState.dpToPx;
26 import static android.server.wm.ActivityManagerState.STATE_RESUMED;
27 import static android.server.wm.ComponentNameUtils.getWindowName;
28 import static android.server.wm.StateLogger.logE;
29 import static android.server.wm.app.Components.BROADCAST_RECEIVER_ACTIVITY;
30 import static android.server.wm.app.Components.DIALOG_WHEN_LARGE_ACTIVITY;
31 import static android.server.wm.app.Components.LANDSCAPE_ORIENTATION_ACTIVITY;
32 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
33 import static android.server.wm.app.Components.NIGHT_MODE_ACTIVITY;
34 import static android.server.wm.app.Components.PORTRAIT_ORIENTATION_ACTIVITY;
35 import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY;
36 import static android.server.wm.app.Components.TEST_ACTIVITY;
37 import static android.server.wm.translucentapp.Components.TRANSLUCENT_LANDSCAPE_ACTIVITY;
38 import static android.server.wm.translucentapp26.Components.SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY;
39 import static android.view.Surface.ROTATION_0;
40 import static android.view.Surface.ROTATION_180;
41 import static android.view.Surface.ROTATION_270;
42 import static android.view.Surface.ROTATION_90;
43 
44 import static org.hamcrest.MatcherAssert.assertThat;
45 import static org.hamcrest.Matchers.lessThan;
46 import static org.junit.Assert.assertEquals;
47 import static org.junit.Assert.assertFalse;
48 import static org.junit.Assert.assertNotEquals;
49 import static org.junit.Assert.assertNotNull;
50 import static org.junit.Assert.assertNull;
51 import static org.junit.Assert.fail;
52 import static org.junit.Assume.assumeFalse;
53 import static org.junit.Assume.assumeTrue;
54 
55 import android.content.ComponentName;
56 import android.content.res.Configuration;
57 import android.graphics.Rect;
58 import android.platform.test.annotations.Presubmit;
59 import android.server.wm.CommandSession.SizeInfo;
60 
61 import androidx.test.filters.FlakyTest;
62 
63 import org.junit.Ignore;
64 import org.junit.Test;
65 
66 import java.util.List;
67 
68 /**
69  * Build/Install/Run:
70  *     atest CtsWindowManagerDeviceTestCases:AppConfigurationTests
71  */
72 @Presubmit
73 public class AppConfigurationTests extends ActivityManagerTestBase {
74 
75     private static final int SMALL_WIDTH_DP = 426;
76     private static final int SMALL_HEIGHT_DP = 320;
77 
78     /**
79      * Tests that the WindowManager#getDefaultDisplay() and the Configuration of the Activity
80      * has an updated size when the Activity is resized from fullscreen to docked state.
81      *
82      * The Activity handles configuration changes, so it will not be restarted between resizes.
83      * On Configuration changes, the Activity logs the Display size and Configuration width
84      * and heights. The values reported in fullscreen should be larger than those reported in
85      * docked state.
86      */
87     @Test
testConfigurationUpdatesWhenResizedFromFullscreen()88     public void testConfigurationUpdatesWhenResizedFromFullscreen() {
89         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
90 
91         separateTestJournal();
92         launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
93         final SizeInfo fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
94 
95         separateTestJournal();
96         setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
97         final SizeInfo dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
98 
99         assertSizesAreSane(fullscreenSizes, dockedSizes);
100     }
101 
102     /**
103      * Same as {@link #testConfigurationUpdatesWhenResizedFromFullscreen()} but resizing
104      * from docked state to fullscreen (reverse).
105      */
106     @Test
107     @FlakyTest(bugId = 71792393)
testConfigurationUpdatesWhenResizedFromDockedStack()108     public void testConfigurationUpdatesWhenResizedFromDockedStack() {
109         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
110 
111         separateTestJournal();
112         launchActivity(RESIZEABLE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
113         final SizeInfo dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
114 
115         separateTestJournal();
116         setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
117         final SizeInfo fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
118 
119         assertSizesAreSane(fullscreenSizes, dockedSizes);
120     }
121 
122     /**
123      * Tests whether the Display sizes change when rotating the device.
124      */
125     @Test
testConfigurationUpdatesWhenRotatingWhileFullscreen()126     public void testConfigurationUpdatesWhenRotatingWhileFullscreen() throws Exception {
127         assumeTrue("Skipping test: no rotation support", supportsRotation());
128 
129         try (final RotationSession rotationSession = new RotationSession()) {
130             rotationSession.set(ROTATION_0);
131 
132             separateTestJournal();
133             launchActivity(RESIZEABLE_ACTIVITY,
134                     WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
135             final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
136 
137             rotateAndCheckSizes(rotationSession, initialSizes);
138         }
139     }
140 
141     /**
142      * Same as {@link #testConfigurationUpdatesWhenRotatingWhileFullscreen()} but when the Activity
143      * is in the docked stack.
144      */
145     @Test
testConfigurationUpdatesWhenRotatingWhileDocked()146     public void testConfigurationUpdatesWhenRotatingWhileDocked() throws Exception {
147         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
148 
149         try (final RotationSession rotationSession = new RotationSession()) {
150             rotationSession.set(ROTATION_0);
151 
152             separateTestJournal();
153             // Launch our own activity to side in case Recents (or other activity to side) doesn't
154             // support rotation.
155             launchActivitiesInSplitScreen(
156                     getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
157                     getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY));
158             // Launch target activity in docked stack.
159             getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY).execute();
160             final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
161 
162             rotateAndCheckSizes(rotationSession, initialSizes);
163         }
164     }
165 
166     /**
167      * Same as {@link #testConfigurationUpdatesWhenRotatingWhileDocked()} but when the Activity
168      * is launched to side from docked stack.
169      */
170     @Test
testConfigurationUpdatesWhenRotatingToSideFromDocked()171     public void testConfigurationUpdatesWhenRotatingToSideFromDocked() throws Exception {
172         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
173 
174         try (final RotationSession rotationSession = new RotationSession()) {
175             rotationSession.set(ROTATION_0);
176 
177             separateTestJournal();
178             launchActivitiesInSplitScreen(
179                     getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
180                     getLaunchActivityBuilder().setTargetActivity(RESIZEABLE_ACTIVITY));
181             final SizeInfo initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
182 
183             rotateAndCheckSizes(rotationSession, initialSizes);
184         }
185     }
186 
rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes)187     private void rotateAndCheckSizes(RotationSession rotationSession, SizeInfo prevSizes)
188             throws Exception {
189         final ActivityManagerState.ActivityTask task =
190                 mAmWmState.getAmState().getTaskByActivity(RESIZEABLE_ACTIVITY);
191         final int displayId = mAmWmState.getAmState().getStackById(task.mStackId).mDisplayId;
192 
193         assumeTrue(supportsLockedUserRotation(rotationSession, displayId));
194 
195         final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
196         for (final int rotation : rotations) {
197             separateTestJournal();
198             rotationSession.set(rotation);
199             final int newDeviceRotation = getDeviceRotation(displayId);
200             if (newDeviceRotation == INVALID_DEVICE_ROTATION) {
201                 logE("Got an invalid device rotation value. "
202                         + "Continuing the test despite of that, but it is likely to fail.");
203             }
204 
205             final SizeInfo rotatedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY);
206             assertSizesRotate(prevSizes, rotatedSizes,
207                     // Skip orientation checks if we are not in fullscreen mode.
208                     task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN);
209             prevSizes = rotatedSizes;
210         }
211     }
212 
213     /**
214      * Tests when activity moved from fullscreen stack to docked and back. Activity will be
215      * relaunched twice and it should have same config as initial one.
216      */
217     @Test
testSameConfigurationFullSplitFullRelaunch()218     public void testSameConfigurationFullSplitFullRelaunch() {
219         moveActivityFullSplitFull(TEST_ACTIVITY);
220     }
221 
222     /**
223      * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
224      */
225     @Test
testSameConfigurationFullSplitFullNoRelaunch()226     public void testSameConfigurationFullSplitFullNoRelaunch() {
227         moveActivityFullSplitFull(RESIZEABLE_ACTIVITY);
228     }
229 
230     /**
231      * Launches activity in fullscreen stack, moves to docked stack and back to fullscreen stack.
232      * Last operation is done in a way which simulates split-screen divider movement maximizing
233      * docked stack size and then moving task to fullscreen stack - the same way it is done when
234      * user long-presses overview/recents button to exit split-screen.
235      * Asserts that initial and final reported sizes in fullscreen stack are the same.
236      */
moveActivityFullSplitFull(ComponentName activityName)237     private void moveActivityFullSplitFull(ComponentName activityName) {
238         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
239 
240         // Launch to fullscreen stack and record size.
241         separateTestJournal();
242         launchActivity(activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
243         final SizeInfo initialFullscreenSizes = getActivityDisplaySize(activityName);
244         final Rect displayRect = getDisplayRect(activityName);
245 
246         // Move to docked stack.
247         separateTestJournal();
248         setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
249         final SizeInfo dockedSizes = getActivityDisplaySize(activityName);
250         assertSizesAreSane(initialFullscreenSizes, dockedSizes);
251         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
252         // will come up.
253         launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
254         mAmWmState.computeState(false /* compareTaskAndStackBounds */,
255                 new WaitForValidActivityState.Builder(activityName).build());
256         final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
257                 .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
258 
259         // Resize docked stack to fullscreen size. This will trigger activity relaunch with
260         // non-empty override configuration corresponding to fullscreen size.
261         separateTestJournal();
262         resizeStack(stack.mStackId, displayRect.left, displayRect.top, displayRect.width(),
263                 displayRect.height());
264 
265         // Move activity back to fullscreen stack.
266         setActivityTaskWindowingMode(activityName,
267                 WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
268         final SizeInfo finalFullscreenSizes = getActivityDisplaySize(activityName);
269 
270         // After activity configuration was changed twice it must report same size as original one.
271         assertSizesAreSame(initialFullscreenSizes, finalFullscreenSizes);
272     }
273 
274     /**
275      * Tests when activity moved from docked stack to fullscreen and back. Activity will be
276      * relaunched twice and it should have same config as initial one.
277      */
278     @Test
279     @FlakyTest
testSameConfigurationSplitFullSplitRelaunch()280     public void testSameConfigurationSplitFullSplitRelaunch() {
281         moveActivitySplitFullSplit(TEST_ACTIVITY);
282     }
283 
284     /**
285      * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
286      */
287     @Test
288     @FlakyTest
testSameConfigurationSplitFullSplitNoRelaunch()289     public void testSameConfigurationSplitFullSplitNoRelaunch() {
290         moveActivitySplitFullSplit(RESIZEABLE_ACTIVITY);
291     }
292 
293     /**
294      * Tests that an activity with the DialogWhenLarge theme can transform properly when in split
295      * screen.
296      */
297     @Test
298     @FlakyTest(bugId = 110276714)
testDialogWhenLargeSplitSmall()299     public void testDialogWhenLargeSplitSmall() {
300         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
301 
302         launchActivity(DIALOG_WHEN_LARGE_ACTIVITY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
303         final ActivityManagerState.ActivityStack stack = mAmWmState.getAmState()
304                 .getStandardStackByWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
305         final WindowManagerState.Display display =
306                 mAmWmState.getWmState().getDisplay(stack.mDisplayId);
307         final int density = display.getDpi();
308         final int smallWidthPx = dpToPx(SMALL_WIDTH_DP, density);
309         final int smallHeightPx = dpToPx(SMALL_HEIGHT_DP, density);
310 
311         resizeStack(stack.mStackId, 0, 0, smallWidthPx, smallHeightPx);
312         mAmWmState.waitForValidState(
313                 new WaitForValidActivityState.Builder(DIALOG_WHEN_LARGE_ACTIVITY)
314                         .setWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
315                         .setActivityType(ACTIVITY_TYPE_STANDARD)
316                         .build());
317     }
318 
319     /**
320      * Test that device handles consequent requested orientations and displays the activities.
321      */
322     @Test
323     @FlakyTest(bugId = 71875755)
testFullscreenAppOrientationRequests()324     public void testFullscreenAppOrientationRequests() {
325         assumeTrue("Skipping test: no rotation support", supportsRotation());
326 
327         separateTestJournal();
328         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
329         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
330         SizeInfo reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
331         assertEquals("portrait activity should be in portrait",
332                 1 /* portrait */, reportedSizes.orientation);
333         separateTestJournal();
334 
335         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
336         mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
337         reportedSizes = getLastReportedSizesForActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
338         assertEquals("landscape activity should be in landscape",
339                 2 /* landscape */, reportedSizes.orientation);
340         separateTestJournal();
341 
342         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
343         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
344         reportedSizes = getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
345         assertEquals("portrait activity should be in portrait",
346                 1 /* portrait */, reportedSizes.orientation);
347     }
348 
349     @Test
testNonfullscreenAppOrientationRequests()350     public void testNonfullscreenAppOrientationRequests() {
351         assumeTrue("Skipping test: no rotation support", supportsRotation());
352 
353         separateTestJournal();
354         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
355         final SizeInfo initialReportedSizes =
356                 getLastReportedSizesForActivity(PORTRAIT_ORIENTATION_ACTIVITY);
357         assertEquals("portrait activity should be in portrait",
358                 1 /* portrait */, initialReportedSizes.orientation);
359         separateTestJournal();
360 
361         launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
362         assertEquals("Legacy non-fullscreen activity requested landscape orientation",
363                 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
364 
365         // TODO(b/36897968): uncomment once we can suppress unsupported configurations
366         // final ReportedSizes updatedReportedSizes =
367         //      getLastReportedSizesForActivity(PORTRAIT_ACTIVITY_NAME, logSeparator);
368         // assertEquals("portrait activity should not have moved from portrait",
369         //         1 /* portrait */, updatedReportedSizes.orientation);
370     }
371 
372     /**
373      * Test that device handles consequent requested orientations and will not report a config
374      * change to an invisible activity.
375      */
376     @Test
377     @FlakyTest
testAppOrientationRequestConfigChanges()378     public void testAppOrientationRequestConfigChanges() {
379         assumeTrue("Skipping test: no rotation support", supportsRotation());
380 
381         separateTestJournal();
382         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
383         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
384 
385         assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
386                 1 /* create */, 1 /* start */, 1 /* resume */,
387                 0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */);
388 
389         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
390         mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
391 
392         assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
393                 1 /* create */, 1 /* start */, 1 /* resume */,
394                 1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
395         assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY,
396                 1 /* create */, 1 /* start */, 1 /* resume */,
397                 0 /* pause */, 0 /* stop */, 0 /* destroy */, 0 /* config */);
398 
399         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
400         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
401 
402         assertLifecycleCounts(PORTRAIT_ORIENTATION_ACTIVITY,
403                 2 /* create */, 2 /* start */, 2 /* resume */,
404                 1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
405         assertLifecycleCounts(LANDSCAPE_ORIENTATION_ACTIVITY,
406                 1 /* create */, 1 /* start */, 1 /* resume */,
407                 1 /* pause */, 1 /* stop */, 0 /* destroy */, 0 /* config */);
408     }
409 
410     /**
411      * Test that device orientation is restored when an activity that requests it is no longer
412      * visible.
413      */
414     @Test
testAppOrientationRequestConfigClears()415     public void testAppOrientationRequestConfigClears() {
416         assumeTrue("Skipping test: no rotation support", supportsRotation());
417 
418         separateTestJournal();
419         launchActivity(TEST_ACTIVITY);
420         mAmWmState.assertVisibility(TEST_ACTIVITY, true /* visible */);
421         final SizeInfo initialReportedSizes = getLastReportedSizesForActivity(TEST_ACTIVITY);
422         final int initialOrientation = initialReportedSizes.orientation;
423 
424         // Launch an activity that requests different orientation and check that it will be applied
425         final boolean launchingPortrait;
426         if (initialOrientation == 2 /* landscape */) {
427             launchingPortrait = true;
428         } else if (initialOrientation == 1 /* portrait */) {
429             launchingPortrait = false;
430         } else {
431             fail("Unexpected orientation value: " + initialOrientation);
432             return;
433         }
434         final ComponentName differentOrientationActivity = launchingPortrait
435                 ? PORTRAIT_ORIENTATION_ACTIVITY : LANDSCAPE_ORIENTATION_ACTIVITY;
436         separateTestJournal();
437         launchActivity(differentOrientationActivity);
438         mAmWmState.assertVisibility(differentOrientationActivity, true /* visible */);
439         final SizeInfo rotatedReportedSizes =
440                 getLastReportedSizesForActivity(differentOrientationActivity);
441         assertEquals("Applied orientation must correspond to activity request",
442                 launchingPortrait ? 1 : 2, rotatedReportedSizes.orientation);
443 
444         // Launch another activity on top and check that its orientation is not affected by previous
445         // activity.
446         separateTestJournal();
447         launchActivity(RESIZEABLE_ACTIVITY);
448         mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
449         final SizeInfo finalReportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
450         assertEquals("Applied orientation must not be influenced by previously visible activity",
451                 initialOrientation, finalReportedSizes.orientation);
452     }
453 
454     // TODO(b/70870253): This test seems malfunction.
455     @Ignore("b/70870253")
456     @Test
testNonFullscreenActivityProhibited()457     public void testNonFullscreenActivityProhibited() {
458         // We do not wait for the activity as it should not launch based on the restrictions around
459         // specifying orientation. We instead start an activity known to launch immediately after
460         // so that we can ensure processing the first activity occurred.
461         launchActivityNoWait(TRANSLUCENT_LANDSCAPE_ACTIVITY);
462         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
463 
464         assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
465                 mAmWmState.getAmState().containsActivity(TRANSLUCENT_LANDSCAPE_ACTIVITY));
466     }
467 
468     @Test
testNonFullscreenActivityPermitted()469     public void testNonFullscreenActivityPermitted() throws Exception {
470         if(!supportsRotation()) {
471             //cannot physically rotate the screen on automotive device, skip
472             return;
473         }
474         try (final RotationSession rotationSession = new RotationSession()) {
475             rotationSession.set(ROTATION_0);
476 
477             launchActivity(SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
478             mAmWmState.assertResumedActivity(
479                     "target SDK <= 26 non-fullscreen activity should be allowed to launch",
480                     SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY);
481             assertEquals("non-fullscreen activity requested landscape orientation",
482                     0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
483         }
484     }
485 
486     /**
487      * Test that device handles moving between two tasks with different orientations.
488      */
489     @Test
testTaskCloseRestoreFixedOrientation()490     public void testTaskCloseRestoreFixedOrientation() {
491         assumeTrue("Skipping test: no rotation support", supportsRotation());
492 
493         // Start landscape activity.
494         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
495         mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
496         assertEquals("Fullscreen app requested landscape orientation",
497                 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
498 
499         // Start another activity in a different task.
500         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
501 
502         // Request portrait
503         mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
504         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
505         waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT);
506 
507         // Finish activity
508         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
509 
510         // Verify that activity brought to front is in originally requested orientation.
511         mAmWmState.computeState(LANDSCAPE_ORIENTATION_ACTIVITY);
512         assertEquals("Should return to app in landscape orientation",
513                 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
514     }
515 
516     /**
517      * Test that device handles moving between two tasks with different orientations.
518      */
519     @Test
testTaskCloseRestoreFreeOrientation()520     public void testTaskCloseRestoreFreeOrientation() {
521         assumeTrue("Skipping test: no rotation support", supportsRotation());
522 
523         // Start landscape activity.
524         launchActivity(RESIZEABLE_ACTIVITY);
525         mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
526         final int initialServerOrientation = mAmWmState.getWmState().getLastOrientation();
527 
528         // Verify fixed-landscape
529         separateTestJournal();
530         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
531         mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_LANDSCAPE);
532         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_LANDSCAPE);
533         waitForBroadcastActivityReady(Configuration.ORIENTATION_LANDSCAPE);
534         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
535 
536         // Verify that activity brought to front is in originally requested orientation.
537         mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
538         SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
539         assertNull("Should come back in original orientation", reportedSizes);
540         assertEquals("Should come back in original server orientation",
541                 initialServerOrientation, mAmWmState.getWmState().getLastOrientation());
542         assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
543                 0 /* numConfigChange */);
544 
545         // Verify fixed-portrait
546         separateTestJournal();
547         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
548         mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
549         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
550         waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT);
551         mBroadcastActionTrigger.finishBroadcastReceiverActivity();
552 
553         // Verify that activity brought to front is in originally requested orientation.
554         mAmWmState.waitForActivityState(RESIZEABLE_ACTIVITY, STATE_RESUMED);
555         reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
556         assertNull("Should come back in original orientation", reportedSizes);
557         assertEquals("Should come back in original server orientation",
558                 initialServerOrientation, mAmWmState.getWmState().getLastOrientation());
559         assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
560                 0 /* numConfigChange */);
561     }
562 
563     /**
564      * Test that activity orientation will change when device is rotated.
565      * Also verify that occluded activity will not get config changes.
566      */
567     @Test
568     @FlakyTest
testAppOrientationWhenRotating()569     public void testAppOrientationWhenRotating() throws Exception {
570         assumeTrue("Skipping test: no rotation support", supportsRotation());
571 
572         // Start resizeable activity that handles configuration changes.
573         separateTestJournal();
574         launchActivity(TEST_ACTIVITY);
575         launchActivity(RESIZEABLE_ACTIVITY);
576         mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY, true /* visible */);
577 
578         final int displayId = mAmWmState.getAmState().getDisplayByActivity(RESIZEABLE_ACTIVITY);
579 
580         // Rotate the activity and check that it receives configuration changes with a different
581         // orientation each time.
582         try (final RotationSession rotationSession = new RotationSession()) {
583             assumeTrue("Skipping test: no locked user rotation mode support.",
584                     supportsLockedUserRotation(rotationSession, displayId));
585 
586             rotationSession.set(ROTATION_0);
587             SizeInfo reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
588             int prevOrientation = reportedSizes.orientation;
589 
590             final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
591             for (final int rotation : rotations) {
592                 separateTestJournal();
593                 rotationSession.set(rotation);
594 
595                 // Verify lifecycle count and orientation changes.
596                 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
597                         1 /* numConfigChange */);
598                 reportedSizes = getLastReportedSizesForActivity(RESIZEABLE_ACTIVITY);
599                 assertNotEquals(prevOrientation, reportedSizes.orientation);
600                 assertRelaunchOrConfigChanged(TEST_ACTIVITY, 0 /* numRelaunch */,
601                         0 /* numConfigChange */);
602 
603                 prevOrientation = reportedSizes.orientation;
604             }
605         }
606     }
607 
608     /**
609      * Test that activity orientation will not change when trying to rotate fixed-orientation
610      * activity.
611      * Also verify that occluded activity will not get config changes.
612      */
613     @Test
testFixedOrientationWhenRotating()614     public void testFixedOrientationWhenRotating() throws Exception {
615         assumeTrue("Skipping test: no rotation support", supportsRotation());
616         // TODO(b/110533226): Fix test on devices with display cutout
617         assumeFalse("Skipping test: display cutout present, can't predict exact lifecycle",
618                 hasDisplayCutout());
619 
620         // Start portrait-fixed activity
621         separateTestJournal();
622         launchActivity(RESIZEABLE_ACTIVITY);
623         launchActivity(PORTRAIT_ORIENTATION_ACTIVITY);
624         mAmWmState.assertVisibility(PORTRAIT_ORIENTATION_ACTIVITY, true /* visible */);
625 
626         final int displayId = mAmWmState.getAmState()
627                 .getDisplayByActivity(PORTRAIT_ORIENTATION_ACTIVITY);
628 
629         // Rotate the activity and check that the orientation doesn't change
630         try (final RotationSession rotationSession = new RotationSession()) {
631             assumeTrue("Skipping test: no user locked rotation support.",
632                     supportsLockedUserRotation(rotationSession, displayId));
633 
634             rotationSession.set(ROTATION_0);
635 
636             final int[] rotations = { ROTATION_270, ROTATION_180, ROTATION_90, ROTATION_0 };
637             for (final int rotation : rotations) {
638                 separateTestJournal();
639                 rotationSession.set(rotation);
640 
641                 // Verify lifecycle count and orientation changes.
642                 assertRelaunchOrConfigChanged(PORTRAIT_ORIENTATION_ACTIVITY, 0 /* numRelaunch */,
643                         0 /* numConfigChange */);
644                 final SizeInfo reportedSizes = getLastReportedSizesForActivity(
645                         PORTRAIT_ORIENTATION_ACTIVITY);
646                 assertNull("No new sizes must be reported", reportedSizes);
647                 assertRelaunchOrConfigChanged(RESIZEABLE_ACTIVITY, 0 /* numRelaunch */,
648                         0 /* numConfigChange */);
649             }
650         }
651     }
652 
653     /**
654      * Test that device handles moving between two tasks with different orientations.
655      */
656     @Test
657     @FlakyTest(bugId = 71792393)
testTaskMoveToBackOrientation()658     public void testTaskMoveToBackOrientation() {
659         assumeTrue("Skipping test: no rotation support", supportsRotation());
660 
661         // Start landscape activity.
662         launchActivity(LANDSCAPE_ORIENTATION_ACTIVITY);
663         mAmWmState.assertVisibility(LANDSCAPE_ORIENTATION_ACTIVITY, true /* visible */);
664         assertEquals("Fullscreen app requested landscape orientation",
665                 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
666 
667         // Start another activity in a different task.
668         launchActivityInNewTask(BROADCAST_RECEIVER_ACTIVITY);
669 
670         // Request portrait
671         mBroadcastActionTrigger.requestOrientation(SCREEN_ORIENTATION_PORTRAIT);
672         mAmWmState.waitForLastOrientation(SCREEN_ORIENTATION_PORTRAIT);
673         waitForBroadcastActivityReady(Configuration.ORIENTATION_PORTRAIT);
674 
675         // Finish activity
676         mBroadcastActionTrigger.moveTopTaskToBack();
677 
678         // Verify that activity brought to front is in originally requested orientation.
679         mAmWmState.waitForValidState(LANDSCAPE_ORIENTATION_ACTIVITY);
680         assertEquals("Should return to app in landscape orientation",
681                 0 /* landscape */, mAmWmState.getWmState().getLastOrientation());
682     }
683 
684     /**
685      * Test that device doesn't change device orientation by app request while in multi-window.
686      */
687     @FlakyTest(bugId = 71918731)
688     @Test
testSplitscreenPortraitAppOrientationRequests()689     public void testSplitscreenPortraitAppOrientationRequests() throws Exception {
690         assumeTrue("Skipping test: no rotation support", supportsRotation());
691         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
692 
693         try (final RotationSession rotationSession = new RotationSession()) {
694             requestOrientationInSplitScreen(rotationSession,
695                     ROTATION_90 /* portrait */, LANDSCAPE_ORIENTATION_ACTIVITY);
696         }
697     }
698 
699     /**
700      * Test that device doesn't change device orientation by app request while in multi-window.
701      */
702     @Test
testSplitscreenLandscapeAppOrientationRequests()703     public void testSplitscreenLandscapeAppOrientationRequests() throws Exception {
704         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
705 
706         try (final RotationSession rotationSession = new RotationSession()) {
707             requestOrientationInSplitScreen(rotationSession,
708                     ROTATION_0 /* landscape */, PORTRAIT_ORIENTATION_ACTIVITY);
709         }
710     }
711 
712     /**
713      * Rotate the device and launch specified activity in split-screen, checking if orientation
714      * didn't change.
715      */
requestOrientationInSplitScreen(RotationSession rotationSession, int orientation, ComponentName activity)716     private void requestOrientationInSplitScreen(RotationSession rotationSession, int orientation,
717             ComponentName activity) throws Exception {
718         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
719 
720         // Set initial orientation.
721         rotationSession.set(orientation);
722 
723         // Launch activities that request orientations and check that device doesn't rotate.
724         launchActivitiesInSplitScreen(
725                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
726                 getLaunchActivityBuilder().setTargetActivity(activity).setMultipleTask(true));
727 
728         mAmWmState.assertVisibility(activity, true /* visible */);
729         assertEquals("Split-screen apps shouldn't influence device orientation",
730                 orientation, mAmWmState.getWmState().getRotation());
731 
732         getLaunchActivityBuilder().setMultipleTask(true).setTargetActivity(activity).execute();
733         mAmWmState.computeState(activity);
734         mAmWmState.assertVisibility(activity, true /* visible */);
735         assertEquals("Split-screen apps shouldn't influence device orientation",
736                 orientation, mAmWmState.getWmState().getRotation());
737     }
738 
739     /**
740      * Launches activity in docked stack, moves to fullscreen stack and back to docked stack.
741      * Asserts that initial and final reported sizes in docked stack are the same.
742      */
moveActivitySplitFullSplit(ComponentName activityName)743     private void moveActivitySplitFullSplit(ComponentName activityName) {
744         assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
745 
746         // Launch to docked stack and record size.
747         separateTestJournal();
748         launchActivityInSplitScreenWithRecents(activityName);
749         final SizeInfo initialDockedSizes = getActivityDisplaySize(activityName);
750         // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
751         // will come up.
752         launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
753         mAmWmState.computeState(false /* compareTaskAndStackBounds */,
754                 new WaitForValidActivityState.Builder(activityName).build());
755 
756         // Move to fullscreen stack.
757         separateTestJournal();
758         setActivityTaskWindowingMode(
759                 activityName, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
760         final SizeInfo fullscreenSizes = getActivityDisplaySize(activityName);
761         assertSizesAreSane(fullscreenSizes, initialDockedSizes);
762 
763         // Move activity back to docked stack.
764         separateTestJournal();
765         setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
766         final SizeInfo finalDockedSizes = getActivityDisplaySize(activityName);
767 
768         // After activity configuration was changed twice it must report same size as original one.
769         assertSizesAreSame(initialDockedSizes, finalDockedSizes);
770     }
771 
772     /**
773      * Asserts that after rotation, the aspect ratios of display size, metrics, and configuration
774      * have flipped.
775      */
assertSizesRotate(SizeInfo rotationA, SizeInfo rotationB, boolean skipOrientationCheck)776     private static void assertSizesRotate(SizeInfo rotationA, SizeInfo rotationB,
777             boolean skipOrientationCheck) {
778         assertEquals(rotationA.displayWidth, rotationA.metricsWidth);
779         assertEquals(rotationA.displayHeight, rotationA.metricsHeight);
780         assertEquals(rotationB.displayWidth, rotationB.metricsWidth);
781         assertEquals(rotationB.displayHeight, rotationB.metricsHeight);
782 
783         if (skipOrientationCheck) {
784             // All done if we are not doing orientation check.
785             return;
786         }
787         final boolean beforePortrait = rotationA.displayWidth < rotationA.displayHeight;
788         final boolean afterPortrait = rotationB.displayWidth < rotationB.displayHeight;
789         assertFalse(beforePortrait == afterPortrait);
790 
791         final boolean beforeConfigPortrait = rotationA.widthDp < rotationA.heightDp;
792         final boolean afterConfigPortrait = rotationB.widthDp < rotationB.heightDp;
793         assertEquals(beforePortrait, beforeConfigPortrait);
794         assertEquals(afterPortrait, afterConfigPortrait);
795 
796         assertEquals(rotationA.smallestWidthDp, rotationB.smallestWidthDp);
797     }
798 
799     /**
800      * Throws an AssertionError if fullscreenSizes has widths/heights (depending on aspect ratio)
801      * that are smaller than the dockedSizes.
802      */
803     private static void assertSizesAreSane(SizeInfo fullscreenSizes, SizeInfo dockedSizes) {
804         final boolean portrait = fullscreenSizes.displayWidth < fullscreenSizes.displayHeight;
805         if (portrait) {
806             assertThat(dockedSizes.displayHeight, lessThan(fullscreenSizes.displayHeight));
807             assertThat(dockedSizes.heightDp, lessThan(fullscreenSizes.heightDp));
808             assertThat(dockedSizes.metricsHeight, lessThan(fullscreenSizes.metricsHeight));
809         } else {
810             assertThat(dockedSizes.displayWidth, lessThan(fullscreenSizes.displayWidth));
811             assertThat(dockedSizes.widthDp, lessThan(fullscreenSizes.widthDp));
812             assertThat(dockedSizes.metricsWidth, lessThan(fullscreenSizes.metricsWidth));
813         }
814     }
815 
816     /**
817      * Throws an AssertionError if sizes are different.
818      */
819     private static void assertSizesAreSame(SizeInfo firstSize, SizeInfo secondSize) {
820         assertEquals(firstSize.widthDp, secondSize.widthDp);
821         assertEquals(firstSize.heightDp, secondSize.heightDp);
822         assertEquals(firstSize.displayWidth, secondSize.displayWidth);
823         assertEquals(firstSize.displayHeight, secondSize.displayHeight);
824         assertEquals(firstSize.metricsWidth, secondSize.metricsWidth);
825         assertEquals(firstSize.metricsHeight, secondSize.metricsHeight);
826         assertEquals(firstSize.smallestWidthDp, secondSize.smallestWidthDp);
827     }
828 
829     private SizeInfo getActivityDisplaySize(ComponentName activityName) {
830         mAmWmState.computeState(false /* compareTaskAndStackBounds */,
831                 new WaitForValidActivityState(activityName));
832         final SizeInfo details = getLastReportedSizesForActivity(activityName);
833         assertNotNull(details);
834         return details;
835     }
836 
837     private Rect getDisplayRect(ComponentName activityName) {
838         final String windowName = getWindowName(activityName);
839 
840         mAmWmState.computeState(activityName);
841         mAmWmState.assertFocusedWindow("Test window must be the front window.", windowName);
842 
843         final List<WindowManagerState.WindowState> windowList =
844                 mAmWmState.getWmState().getMatchingVisibleWindowState(windowName);
845 
846         assertEquals("Should have exactly one window state for the activity.", 1,
847                 windowList.size());
848 
849         WindowManagerState.WindowState windowState = windowList.get(0);
850         assertNotNull("Should have a valid window", windowState);
851 
852         WindowManagerState.Display display = mAmWmState.getWmState()
853                 .getDisplay(windowState.getDisplayId());
854         assertNotNull("Should be on a display", display);
855 
856         return display.getDisplayRect();
857     }
858 
859     private void waitForBroadcastActivityReady(int orientation) {
860         mAmWmState.waitForActivityOrientation(BROADCAST_RECEIVER_ACTIVITY, orientation);
861         mAmWmState.waitForActivityState(BROADCAST_RECEIVER_ACTIVITY, STATE_RESUMED);
862     }
863 
864     /**
865      * Test launching an activity which requests specific UI mode during creation.
866      */
867     @Test
868     public void testLaunchWithUiModeChange() {
869         // Launch activity that changes UI mode and handles this configuration change.
870         launchActivity(NIGHT_MODE_ACTIVITY);
871         mAmWmState.waitForActivityState(NIGHT_MODE_ACTIVITY, STATE_RESUMED);
872 
873         // Check if activity is launched successfully.
874         mAmWmState.assertVisibility(NIGHT_MODE_ACTIVITY, true /* visible */);
875         mAmWmState.assertFocusedActivity("Launched activity should be focused",
876                 NIGHT_MODE_ACTIVITY);
877         mAmWmState.assertResumedActivity("Launched activity must be resumed", NIGHT_MODE_ACTIVITY);
878     }
879 }
880