1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of 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,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.server.wm;
18 
19 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
20 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
21 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
22 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
23 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
24 import static android.view.Display.DEFAULT_DISPLAY;
25 
26 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
27 
28 import static com.google.common.truth.Truth.assertThat;
29 
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertFalse;
32 import static org.junit.Assert.assertNotNull;
33 import static org.junit.Assert.assertTrue;
34 import static org.junit.Assume.assumeFalse;
35 
36 import android.app.Activity;
37 import android.compat.testing.PlatformCompatChangeRule;
38 import android.content.ComponentName;
39 import android.content.pm.ActivityInfo;
40 import android.content.pm.PackageManager;
41 import android.graphics.Rect;
42 import android.platform.test.annotations.Presubmit;
43 import android.provider.DeviceConfig;
44 import android.provider.DeviceConfig.Properties;
45 import android.server.wm.WindowManagerTestBase.FocusableActivity;
46 import android.util.Size;
47 
48 import androidx.annotation.Nullable;
49 import androidx.test.filters.FlakyTest;
50 
51 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
52 
53 import org.junit.Before;
54 import org.junit.Rule;
55 import org.junit.Test;
56 import org.junit.rules.TestRule;
57 
58 /**
59  * The test is focused on compatibility changes that have an effect on WM logic, and tests that
60  * enabling these changes has the correct effect.
61  *
62  * This is achieved by launching a custom activity with certain properties (e.g., a resizeable
63  * portrait activity) that behaves in a certain way (e.g., enter size compat mode after resizing the
64  * display) and enabling a compatibility change (e.g., {@link ActivityInfo#FORCE_RESIZE_APP}) that
65  * changes that behavior (e.g., not enter size compat mode).
66  *
67  * The behavior without enabling a compatibility change is also tested as a baseline.
68  *
69  * <p>Build/Install/Run:
70  * atest CtsWindowManagerDeviceTestCases:CompatChangeTests
71  */
72 @Presubmit
73 public final class CompatChangeTests extends MultiDisplayTestBase {
74     private static final ComponentName RESIZEABLE_PORTRAIT_ACTIVITY =
75             component(ResizeablePortraitActivity.class);
76     private static final ComponentName RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY =
77             component(ResizeableLargeAspectRatioActivity.class);
78     private static final ComponentName NON_RESIZEABLE_PORTRAIT_ACTIVITY =
79             component(NonResizeablePortraitActivity.class);
80     private static final ComponentName NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY =
81             component(NonResizeableAspectRatioActivity.class);
82     private static final ComponentName NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY =
83             component(NonResizeableLargeAspectRatioActivity.class);
84     private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY =
85             component(SupportsSizeChangesPortraitActivity.class);
86 
87     // Device aspect ratio (both portrait and landscape orientations) for min aspect ratio tests
88     private static final float SIZE_COMPAT_DISPLAY_ASPECT_RATIO = 1.4f;
89     // Fixed orientation min aspect ratio
90     private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f;
91     // The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest).
92     private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f;
93     // The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the
94     // manifest). This needs to be higher than the aspect ratio of any device, which according to
95     // CDD is at most 21:9.
96     private static final float ACTIVITY_LARGE_MIN_ASPECT_RATIO = 3f;
97 
98     private static final float FLOAT_EQUALITY_DELTA = 0.01f;
99 
100     @Rule
101     public TestRule compatChangeRule = new PlatformCompatChangeRule();
102 
103     private DisplayMetricsSession mDisplayMetricsSession;
104 
105     @Before
106     @Override
setUp()107     public void setUp() throws Exception {
108         super.setUp();
109 
110         mDisplayMetricsSession =
111                 createManagedDisplayMetricsSession(DEFAULT_DISPLAY);
112         createManagedLetterboxAspectRatioSession(DEFAULT_DISPLAY,
113                 FIXED_ORIENTATION_MIN_ASPECT_RATIO);
114         createManagedConstrainDisplayApisFlagsSession();
115     }
116 
117     /**
118      * Test that a non-resizeable portrait activity enters size compat mode after resizing the
119      * display.
120      */
121     @Test
testSizeCompatForNonResizeableActivity()122     public void testSizeCompatForNonResizeableActivity() {
123         runSizeCompatTest(
124                 NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ true);
125     }
126 
127     /**
128      * Test that a non-resizeable portrait activity doesn't enter size compat mode after resizing
129      * the display, when the {@link ActivityInfo#FORCE_RESIZE_APP} compat change is enabled.
130      */
131     @Test
132     @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
testSizeCompatForNonResizeableActivityForceResizeEnabled()133     public void testSizeCompatForNonResizeableActivityForceResizeEnabled() {
134         runSizeCompatTest(
135                 NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ false);
136     }
137 
138     /**
139      * Test that a resizeable portrait activity doesn't enter size compat mode after resizing
140      * the display.
141      */
142     @Test
testSizeCompatForResizeableActivity()143     public void testSizeCompatForResizeableActivity() {
144         runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY,  /* inSizeCompatModeAfterResize= */ false);
145     }
146 
147     /**
148      * Test that a non-resizeable portrait activity that supports size changes doesn't enter size
149      * compat mode after resizing the display.
150      */
151     @Test
testSizeCompatForSupportsSizeChangesActivity()152     public void testSizeCompatForSupportsSizeChangesActivity() {
153         runSizeCompatTest(
154                 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ false);
155     }
156 
157     /**
158      * Test that a resizeable portrait activity enters size compat mode after resizing
159      * the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat change is enabled.
160      */
161     @Test
162     @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
testSizeCompatForResizeableActivityForceNonResizeEnabled()163     public void testSizeCompatForResizeableActivityForceNonResizeEnabled() {
164         runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ true);
165     }
166 
167     /**
168      * Test that a non-resizeable portrait activity that supports size changes enters size compat
169      * mode after resizing the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat
170      * change is enabled.
171      */
172     @Test
173     @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled()174     public void testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled() {
175         runSizeCompatTest(
176                 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize= */ true);
177     }
178 
179     /**
180      * Test that a min aspect ratio activity eligible for size compat mode results in sandboxed
181      * Display APIs.
182      */
183     @Test
testSandboxForNonResizableAspectRatioActivity()184     public void testSandboxForNonResizableAspectRatioActivity() {
185         runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ true);
186     }
187 
188     /**
189      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
190      * APIs sandboxed when the {@link ActivityInfo#NEVER_SANDBOX_DISPLAY_APIS} compat change is
191      * enabled.
192      */
193     @Test
194     @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled()195     public void testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled() {
196         runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ false);
197     }
198 
199     /**
200      * Test that a min aspect ratio activity eligible for size compat mode does not have the
201      * Display APIs sandboxed when the 'never_constrain_display_apis_all_packages' Device Config
202      * flag is true.
203      */
204     @Test
testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue()205     public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue() {
206         setNeverConstrainDisplayApisAllPackagesFlag("true");
207         // Setting 'never_constrain_display_apis' as well to make sure it is ignored.
208         setNeverConstrainDisplayApisFlag("com.android.other::");
209         runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ false);
210     }
211 
212     /**
213      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
214      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
215      * package with an open ended range.
216      */
217     @Test
testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag()218     public void testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag() {
219         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
220         setNeverConstrainDisplayApisFlag(
221                 "com.android.other::," + activity.getPackageName() + "::");
222         runSandboxTest(activity, /* isSandboxed= */ false);
223     }
224 
225     /**
226      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
227      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
228      * package with a version range that matches the installed version of the package.
229      */
230     @Test
testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig()231     public void testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig() {
232         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
233         long version = getPackageVersion(activity);
234         setNeverConstrainDisplayApisFlag(
235                 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
236                         version - 1) + ":" + String.valueOf(version + 1));
237         runSandboxTest(activity, /* isSandboxed= */ false);
238     }
239 
240     /**
241      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
242      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
243      * package with a version range that doesn't match the installed version of the package.
244      */
245     @Test
testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig()246     public void testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig() {
247         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
248         long version = getPackageVersion(activity);
249         setNeverConstrainDisplayApisFlag(
250                 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
251                         version + 1) + ":");
252         runSandboxTest(activity, /* isSandboxed= */ true);
253     }
254 
255     /**
256      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
257      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag doesn't contain the
258      * test package.
259      */
260     @Test
testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag()261     public void testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag() {
262         setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::");
263         runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ true);
264     }
265 
266     /**
267      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
268      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag is empty.
269      */
270     @Test
testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty()271     public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty() {
272         setNeverConstrainDisplayApisFlag("");
273         runSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */ true);
274     }
275 
276     /**
277      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
278      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains an invalid
279      * entry for the test package.
280      */
281     @Test
testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag()282     public void testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag() {
283         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
284         setNeverConstrainDisplayApisFlag(
285                 "com.android.other::," + activity.getPackageName() + ":::");
286         runSandboxTest(activity, /* isSandboxed= */ true);
287     }
288 
289     /**
290      * Test that a min aspect ratio activity not eligible for size compat mode does have the
291      * Display APIs sandboxed when the {@link ActivityInfo#ALWAYS_SANDBOX_DISPLAY_APIS} compat
292      * change is enabled.
293      */
294     @Test
295     @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
testSandboxForResizableAspectRatioActivityAlwaysSandboxDisplayApisEnabled()296     public void testSandboxForResizableAspectRatioActivityAlwaysSandboxDisplayApisEnabled() {
297         runSandboxTest(RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */
298                 true, /* inSizeCompatModeAfterResize= */ false);
299     }
300 
301     /**
302      * Test that a min aspect ratio activity non eligible for size compat mode does not have the
303      * Display APIs sandboxed when the 'always_constrain_display_apis' Device Config flag is empty.
304      */
305     @Test
testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty()306     public void testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty() {
307         setAlwaysConstrainDisplayApisFlag("");
308         runSandboxTest(RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */
309                 false, /* inSizeCompatModeAfterResize= */ false);
310     }
311 
312     /**
313      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
314      * APIs sandboxed when the 'always_constrain_display_apis' Device Config flag contains the test
315      * package.
316      */
317     @Test
testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag()318     public void testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag() {
319         ComponentName activity = RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
320         setAlwaysConstrainDisplayApisFlag(
321                 "com.android.other::," + activity.getPackageName() + "::");
322         runSandboxTest(activity, /* isSandboxed= */ true, /* inSizeCompatModeAfterResize= */ false);
323     }
324 
325     /**
326      * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO} has no effect on its
327      * own. The aspect ratio of the activity should be the same as that of the task, which should be
328      * in line with that of the display.
329      */
330     @Test
331     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
testOverrideMinAspectRatioMissingSpecificOverride()332     public void testOverrideMinAspectRatioMissingSpecificOverride() {
333         // Note that we're using getBounds() in portrait, rather than getAppBounds() like other
334         // tests, because we're comparing to the display size and therefore need to consider insets.
335         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
336                 /* expected= */ SIZE_COMPAT_DISPLAY_ASPECT_RATIO,
337                 /* useAppBoundsInPortrait= */false);
338     }
339 
340     /**
341      * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
342      * its own without the presence of {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO}.
343      */
344     @Test
345     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioMissingGeneralOverride()346     public void testOverrideMinAspectRatioMissingGeneralOverride() {
347         // Note that we're using getBounds() in portrait, rather than getAppBounds() like other
348         // tests, because we're comparing to the display size and therefore need to consider insets.
349         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
350                 /* expected= */ SIZE_COMPAT_DISPLAY_ASPECT_RATIO,
351                 /* useAppBoundsInPortrait= */false);
352     }
353 
354     /**
355      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} sets the min aspect
356      * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE}.
357      */
358     @Test
359     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
360             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioLargeAspectRatio()361     public void testOverrideMinAspectRatioLargeAspectRatio() {
362         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
363                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
364     }
365 
366     /**
367      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM} sets the min aspect
368      * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE}.
369      */
370     @Test
371     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
372             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioMediumAspectRatio()373     public void testOverrideMinAspectRatioMediumAspectRatio() {
374         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
375                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
376     }
377 
378     /**
379      * Test that applying multiple min aspect ratio overrides result in the largest one taking
380      * effect.
381      */
382     @Test
383     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
384             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
385             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioBothAspectRatios()386     public void testOverrideMinAspectRatioBothAspectRatios() {
387         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
388                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
389     }
390 
391     /**
392      * Test that the min aspect ratio of the activity as defined in the manifest is ignored if
393      * there is an override for a larger min aspect ratio present (16:9 > 1.6).
394      */
395     @Test
396     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
397             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride()398     public void testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride() {
399         runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
400                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
401     }
402 
403     /**
404      * Test that the min aspect ratio of the activity as defined in the manifest is upheld if
405      * there is a n override for a smaller min aspect ratio present (3:2 < 1.6).
406      */
407     @Test
408     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
409             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride()410     public void testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride() {
411         runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
412                 /* expected= */ ACTIVITY_MIN_ASPECT_RATIO);
413     }
414 
415     /**
416      * Launches the provided activity into size compat mode twice. The first time, the display
417      * is resized to be half the size. The second time, the display is resized to be twice the
418      * original size.
419      *
420      * @param activity                    the activity under test.
421      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
422      *                                    resizing the display
423      */
runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize)424     private void runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize) {
425         runSizeCompatTest(activity, /* resizeRatio= */ 0.5, inSizeCompatModeAfterResize);
426         restoreDisplay(activity);
427         runSizeCompatTest(activity, /* resizeRatio= */ 2, inSizeCompatModeAfterResize);
428     }
429 
430     /**
431      * Launches the provided activity on the default display, initially not in size compat mode.
432      * After resizing the display, verifies if activity is in size compat mode or not
433      *
434      * @param activity                    the activity under test
435      * @param resizeRatio                 the ratio to resize the display
436      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
437      *                                    resizing the display
438      */
runSizeCompatTest(ComponentName activity, double resizeRatio, boolean inSizeCompatModeAfterResize)439     private void runSizeCompatTest(ComponentName activity, double resizeRatio,
440             boolean inSizeCompatModeAfterResize) {
441         launchActivity(activity);
442 
443         assertSizeCompatMode(activity, /* expectedInSizeCompatMode= */ false);
444 
445         resizeDisplay(activity, resizeRatio);
446 
447         assertSizeCompatMode(activity, inSizeCompatModeAfterResize);
448     }
449 
assertSizeCompatMode(ComponentName activity, boolean expectedInSizeCompatMode)450     private void assertSizeCompatMode(ComponentName activity, boolean expectedInSizeCompatMode) {
451         WindowManagerState.Activity activityContainer = mWmState.getActivity(activity);
452         assertNotNull(activityContainer);
453         if (expectedInSizeCompatMode) {
454             assertTrue("The Window must be in size compat mode",
455                     activityContainer.inSizeCompatMode());
456         } else {
457             assertFalse("The Window must not be in size compat mode",
458                     activityContainer.inSizeCompatMode());
459         }
460     }
461 
runSandboxTest(ComponentName activity, boolean isSandboxed)462     private void runSandboxTest(ComponentName activity, boolean isSandboxed) {
463         runSandboxTest(activity, isSandboxed, /* inSizeCompatModeAfterResize= */ true);
464     }
465 
466     /**
467      * Similar to {@link #runSizeCompatTest(ComponentName, boolean)}, but the activity is expected
468      * to be in size compat mode after resizing the display.
469      *
470      * @param activity                    the activity under test
471      * @param isSandboxed                 when {@code true}, {@link android.app.WindowConfiguration#getMaxBounds()}
472      *                                    are sandboxed to the activity bounds. Otherwise, they inherit the
473      *                                    DisplayArea bounds
474      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
475      *                                    resizing the display
476      */
runSandboxTest(ComponentName activity, boolean isSandboxed, boolean inSizeCompatModeAfterResize)477     private void runSandboxTest(ComponentName activity, boolean isSandboxed,
478             boolean inSizeCompatModeAfterResize) {
479         assertThat(getInitialDisplayAspectRatio()).isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
480         runSizeCompatTest(activity, /* resizeRatio= */ 0.5, inSizeCompatModeAfterResize);
481         assertSandboxed(activity, isSandboxed);
482         restoreDisplay(activity);
483         runSizeCompatTest(activity, /* resizeRatio= */ 2, inSizeCompatModeAfterResize);
484         assertSandboxed(activity, isSandboxed);
485     }
486 
assertSandboxed(ComponentName activityName, boolean expectedSandboxed)487     private void assertSandboxed(ComponentName activityName, boolean expectedSandboxed) {
488         mWmState.computeState(new WaitForValidActivityState(activityName));
489         final WindowManagerState.Activity activity = mWmState.getActivity(activityName);
490         assertNotNull(activity);
491         final Rect activityBounds = activity.getBounds();
492         final Rect maxBounds = activity.getMaxBounds();
493         WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
494         assertNotNull(tda);
495         if (expectedSandboxed) {
496             assertEquals(
497                     "The Window has max bounds sandboxed to the window bounds",
498                     activityBounds, maxBounds);
499         } else {
500             assertEquals(
501                     "The Window is not sandboxed, with max bounds reflecting the DisplayArea",
502                     tda.getBounds(), maxBounds);
503         }
504     }
505 
506     private class ConstrainDisplayApisFlagsSession implements AutoCloseable {
507         private Properties mInitialProperties;
508 
ConstrainDisplayApisFlagsSession()509         ConstrainDisplayApisFlagsSession() {
510             runWithShellPermission(
511                     () -> {
512                         mInitialProperties = DeviceConfig.getProperties(
513                                 NAMESPACE_CONSTRAIN_DISPLAY_APIS);
514                         try {
515                             DeviceConfig.setProperties(new Properties.Builder(
516                                     NAMESPACE_CONSTRAIN_DISPLAY_APIS).build());
517                         } catch (Exception e) {
518                         }
519                     });
520         }
521 
522         @Override
close()523         public void close() {
524             runWithShellPermission(
525                     () -> {
526                         try {
527                             DeviceConfig.setProperties(mInitialProperties);
528                         } catch (Exception e) {
529                         }
530                     });
531         }
532     }
533 
534     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedConstrainDisplayApisFlagsSession()535     private ConstrainDisplayApisFlagsSession createManagedConstrainDisplayApisFlagsSession() {
536         return mObjectTracker.manage(new ConstrainDisplayApisFlagsSession());
537     }
538 
setNeverConstrainDisplayApisFlag(@ullable String value)539     private void setNeverConstrainDisplayApisFlag(@Nullable String value) {
540         setConstrainDisplayApisFlag("never_constrain_display_apis", value);
541     }
542 
setNeverConstrainDisplayApisAllPackagesFlag(@ullable String value)543     private void setNeverConstrainDisplayApisAllPackagesFlag(@Nullable String value) {
544         setConstrainDisplayApisFlag("never_constrain_display_apis_all_packages", value);
545     }
546 
setAlwaysConstrainDisplayApisFlag(@ullable String value)547     private void setAlwaysConstrainDisplayApisFlag(@Nullable String value) {
548         setConstrainDisplayApisFlag("always_constrain_display_apis", value);
549     }
550 
setConstrainDisplayApisFlag(String flagName, @Nullable String value)551     private void setConstrainDisplayApisFlag(String flagName, @Nullable String value) {
552         runWithShellPermission(
553                 () -> {
554                     DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS, flagName,
555                             value, /* makeDefault= */ false);
556                 });
557     }
558 
559     /**
560      * Launches the provided activity twice. The first time, the display is resized to a portrait
561      * aspect ratio. The second time, the display is resized to a landscape aspect ratio.
562      *
563      * @param activity the activity under test.
564      * @param expected the expected aspect ratio in both portrait and landscape displays.
565      */
runMinAspectRatioTest(ComponentName activity, float expected)566     private void runMinAspectRatioTest(ComponentName activity, float expected) {
567         runMinAspectRatioTest(activity, expected, /* useAppBoundsInPortrait= */ true);
568     }
569 
570     /**
571      * Launches the provided activity twice. The first time, the display is resized to a portrait
572      * aspect ratio. The second time, the display is resized to a landscape aspect ratio.
573      *
574      * @param activity               the activity under test.
575      * @param expected               the expected aspect ratio in both a portrait and a landscape
576      *                               display.
577      * @param useAppBoundsInPortrait whether to use {@code activity#getAppBounds} rather than
578      *                               {@code activity.getBounds} in portrait display.
579      */
runMinAspectRatioTest(ComponentName activity, float expected, boolean useAppBoundsInPortrait)580     private void runMinAspectRatioTest(ComponentName activity, float expected,
581             boolean useAppBoundsInPortrait) {
582         // Change the aspect ratio of the display to something that is smaller than all the aspect
583         // ratios used throughout those tests but still portrait. This ensures we're using
584         // enforcing aspect ratio behaviour within orientation.
585         // NOTE: using a smaller aspect ratio (e.g., 1.2) might cause activities to have a landscape
586         // window because of insets.
587         mDisplayMetricsSession.changeAspectRatio(SIZE_COMPAT_DISPLAY_ASPECT_RATIO,
588                 ORIENTATION_PORTRAIT);
589         launchActivity(activity);
590         assertEquals(expected,
591                 getActivityAspectRatio(activity, /* useAppBounds= */ useAppBoundsInPortrait),
592                 FLOAT_EQUALITY_DELTA);
593 
594         // Change the orientation of the display to landscape. In this case we should see
595         // fixed orientation letterboxing and the aspect ratio should be applied there.
596         mDisplayMetricsSession.changeAspectRatio(SIZE_COMPAT_DISPLAY_ASPECT_RATIO,
597                 ORIENTATION_LANDSCAPE);
598         launchActivity(activity);
599         assertEquals(expected,
600                 getActivityAspectRatio(activity, /* useAppBounds= */ true),
601                 FLOAT_EQUALITY_DELTA);
602     }
603 
604     /**
605      * Restore the display size and ensure configuration changes are complete.
606      */
restoreDisplay(ComponentName activity)607     private void restoreDisplay(ComponentName activity) {
608         final Rect originalTaskBounds = mWmState.getTaskByActivity(activity).getBounds();
609         mDisplayMetricsSession.restoreDisplayMetrics();
610         // Ensure configuration changes are complete after resizing the display.
611         waitForTaskBoundsChanged(activity, originalTaskBounds);
612     }
613 
614     /**
615      * Resize the display and ensure configuration changes are complete.
616      */
resizeDisplay(ComponentName activity, double sizeRatio)617     private void resizeDisplay(ComponentName activity, double sizeRatio) {
618         Size originalDisplaySize = mDisplayMetricsSession.getInitialDisplayMetrics().getSize();
619         final Rect originalTaskBounds = mWmState.getTaskByActivity(activity).getBounds();
620         mDisplayMetricsSession.changeDisplayMetrics(sizeRatio, /* densityRatio= */ 1);
621         mWmState.computeState(new WaitForValidActivityState(activity));
622 
623         Size currentDisplaySize = mDisplayMetricsSession.getDisplayMetrics().getSize();
624         assumeFalse("If a display size is capped, resizing may be a no-op",
625             originalDisplaySize.equals(currentDisplaySize));
626 
627         // Ensure configuration changes are complete after resizing the display.
628         waitForTaskBoundsChanged(activity, originalTaskBounds);
629     }
630 
631     /**
632      * Waits until the given activity has updated task bounds.
633      */
waitForTaskBoundsChanged(ComponentName activityName, Rect priorTaskBounds)634     private void waitForTaskBoundsChanged(ComponentName activityName, Rect priorTaskBounds) {
635         mWmState.waitForWithAmState(wmState -> {
636             WindowManagerState.ActivityTask task = wmState.getTaskByActivity(activityName);
637             return task != null && !task.getBounds().equals(priorTaskBounds);
638         }, "checking task bounds updated");
639     }
640 
getActivityAspectRatio(ComponentName componentName, boolean useAppBounds)641     private float getActivityAspectRatio(ComponentName componentName, boolean useAppBounds) {
642         WindowManagerState.Activity activity = mWmState.getActivity(componentName);
643         assertNotNull(activity);
644         Rect bounds = useAppBounds ? activity.getAppBounds() : activity.getBounds();
645         assertNotNull(bounds);
646         return Math.max(bounds.height(), bounds.width())
647                 / (float) (Math.min(bounds.height(), bounds.width()));
648     }
649 
getInitialDisplayAspectRatio()650     private float getInitialDisplayAspectRatio() {
651         Size size = mDisplayMetricsSession.getInitialDisplayMetrics().getSize();
652         return Math.max(size.getHeight(), size.getWidth())
653                 / (float) (Math.min(size.getHeight(), size.getWidth()));
654     }
655 
launchActivity(ComponentName activity)656     private void launchActivity(ComponentName activity) {
657         getLaunchActivityBuilder()
658                 .setDisplayId(DEFAULT_DISPLAY)
659                 .setTargetActivity(activity)
660                 .setUseInstrumentation()
661                 .execute();
662     }
663 
getPackageVersion(ComponentName activity)664     private long getPackageVersion(ComponentName activity) {
665         try {
666             return mContext.getPackageManager().getPackageInfo(activity.getPackageName(),
667                     /* flags= */ 0).getLongVersionCode();
668         } catch (PackageManager.NameNotFoundException e) {
669             throw new RuntimeException(e);
670         }
671     }
672 
component(Class<? extends Activity> activity)673     private static ComponentName component(Class<? extends Activity> activity) {
674         return new ComponentName(getInstrumentation().getContext(), activity);
675     }
676 
677     public static class ResizeablePortraitActivity extends FocusableActivity {
678     }
679 
680     public static class ResizeableLargeAspectRatioActivity extends FocusableActivity {
681     }
682 
683     public static class NonResizeablePortraitActivity extends FocusableActivity {
684     }
685 
686     public static class NonResizeableAspectRatioActivity extends FocusableActivity {
687     }
688 
689     public static class NonResizeableLargeAspectRatioActivity extends FocusableActivity {
690     }
691 
692     public static class SupportsSizeChangesPortraitActivity extends FocusableActivity {
693     }
694 }
695