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.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
20 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
21 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
22 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
23 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
24 import static android.view.Display.DEFAULT_DISPLAY;
25 import static android.view.Surface.ROTATION_90;
26 
27 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
28 
29 import static com.google.common.truth.Truth.assertThat;
30 
31 import static org.junit.Assert.assertEquals;
32 import static org.junit.Assert.assertFalse;
33 import static org.junit.Assert.assertNotNull;
34 import static org.junit.Assert.assertTrue;
35 import static org.junit.Assume.assumeFalse;
36 
37 import android.app.Activity;
38 import android.compat.testing.PlatformCompatChangeRule;
39 import android.content.ComponentName;
40 import android.content.pm.ActivityInfo;
41 import android.content.pm.PackageManager;
42 import android.graphics.Rect;
43 import android.platform.test.annotations.Presubmit;
44 import android.provider.DeviceConfig;
45 import android.provider.DeviceConfig.Properties;
46 import android.server.wm.WindowManagerTestBase.FocusableActivity;
47 import android.util.Size;
48 
49 import androidx.annotation.Nullable;
50 
51 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
52 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
53 
54 import org.junit.Before;
55 import org.junit.Rule;
56 import org.junit.Test;
57 import org.junit.rules.TestRule;
58 
59 /**
60  * The test is focused on compatibility changes that have an effect on WM logic, and tests that
61  * enabling these changes has the correct effect.
62  *
63  * This is achieved by launching a custom activity with certain properties (e.g., a resizeable
64  * portrait activity) that behaves in a certain way (e.g., enter size compat mode after resizing the
65  * display) and enabling a compatibility change (e.g., {@link ActivityInfo#FORCE_RESIZE_APP}) that
66  * changes that behavior (e.g., not enter size compat mode).
67  *
68  * The behavior without enabling a compatibility change is also tested as a baseline.
69  *
70  * <p>Build/Install/Run:
71  * atest CtsWindowManagerDeviceTestCases:CompatChangeTests
72  */
73 @Presubmit
74 public final class CompatChangeTests extends MultiDisplayTestBase {
75     private static final ComponentName RESIZEABLE_PORTRAIT_ACTIVITY =
76             component(ResizeablePortraitActivity.class);
77     private static final ComponentName NON_RESIZEABLE_PORTRAIT_ACTIVITY =
78             component(NonResizeablePortraitActivity.class);
79     private static final ComponentName NON_RESIZEABLE_LANDSCAPE_ACTIVITY =
80             component(NonResizeableLandscapeActivity.class);
81     private static final ComponentName NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY =
82             component(NonResizeableNonFixedOrientationActivity.class);
83     private static final ComponentName NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY =
84             component(NonResizeableAspectRatioActivity.class);
85     private static final ComponentName NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY =
86             component(NonResizeableLargeAspectRatioActivity.class);
87     private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY =
88             component(SupportsSizeChangesPortraitActivity.class);
89 
90     // The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest).
91     private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f;
92     // The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the
93     // manifest). This needs to be higher than the aspect ratio of any device, which according to
94     // CDD is at most 21:9.
95     private static final float ACTIVITY_LARGE_MIN_ASPECT_RATIO = 3f;
96 
97     private static final float FLOAT_EQUALITY_DELTA = 0.01f;
98 
99     @Rule
100     public TestRule compatChangeRule = new PlatformCompatChangeRule();
101 
102     private DisplayMetricsSession mDisplayMetricsSession;
103 
104     @Before
105     @Override
setUp()106     public void setUp() throws Exception {
107         super.setUp();
108 
109         mDisplayMetricsSession =
110                 createManagedDisplayMetricsSession(DEFAULT_DISPLAY);
111         createManagedIgnoreOrientationRequestSession(DEFAULT_DISPLAY, /* value=  */ true);
112         createManagedConstrainDisplayApisFlagsSession();
113     }
114 
115     /**
116      * Test that a non-resizeable portrait activity enters size compat mode after resizing the
117      * display.
118      */
119     @Test
testSizeCompatForNonResizeableActivity()120     public void testSizeCompatForNonResizeableActivity() {
121         runSizeCompatTest(
122                 NON_RESIZEABLE_PORTRAIT_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
123                 /* inSizeCompatModeAfterResize= */ true);
124     }
125 
126     /**
127      * Test that a non-resizeable portrait activity doesn't enter size compat mode after resizing
128      * the display, when the {@link ActivityInfo#FORCE_RESIZE_APP} compat change is enabled.
129      */
130     @Test
131     @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
testSizeCompatForNonResizeableActivityForceResizeEnabled()132     public void testSizeCompatForNonResizeableActivityForceResizeEnabled() {
133         runSizeCompatTest(
134                 NON_RESIZEABLE_PORTRAIT_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
135                 /* 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, WINDOWING_MODE_FULLSCREEN,
145                 /* inSizeCompatModeAfterResize= */ false);
146     }
147 
148     /**
149      * Test that a non-resizeable portrait activity that supports size changes doesn't enter size
150      * compat mode after resizing the display.
151      */
152     @Test
testSizeCompatForSupportsSizeChangesActivity()153     public void testSizeCompatForSupportsSizeChangesActivity() {
154         runSizeCompatTest(
155                 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
156                 /* inSizeCompatModeAfterResize= */ false);
157     }
158 
159     /**
160      * Test that a resizeable portrait activity enters size compat mode after resizing
161      * the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat change is enabled.
162      */
163     @Test
164     @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
testSizeCompatForResizeableActivityForceNonResizeEnabled()165     public void testSizeCompatForResizeableActivityForceNonResizeEnabled() {
166         runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
167                 /* inSizeCompatModeAfterResize= */ true);
168     }
169 
170     /**
171      * Test that a non-resizeable portrait activity that supports size changes enters size compat
172      * mode after resizing the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat
173      * change is enabled.
174      */
175     @Test
176     @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled()177     public void testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled() {
178         runSizeCompatTest(
179                 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
180                 /* inSizeCompatModeAfterResize= */ true);
181     }
182 
183     /**
184      * Test that a min aspect ratio activity eligible for size compat mode results in sandboxed
185      * Display APIs.
186      */
187     @Test
testSandboxForNonResizableAspectRatioActivity()188     public void testSandboxForNonResizableAspectRatioActivity() {
189         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
190                 /* isSandboxed= */ true);
191         assertSandboxedByBounds(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY, /* isSandboxed= */
192                 true);
193     }
194 
195      // =================
196      // NEVER_SANDBOX test cases
197      // =================
198      // Validates that an activity forced into size compat mode never has sandboxing applied to the
199      // max bounds. It is expected that an activity in size compat mode normally always has
200      // sandboxing applied.
201 
202     /**
203      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
204      * APIs sandboxed when the {@link ActivityInfo#NEVER_SANDBOX_DISPLAY_APIS} compat change is
205      * enabled.
206      */
207     @Test
208     @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled()209     public void testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled() {
210         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
211                 /* isSandboxed= */ false);
212     }
213 
214     /**
215      * Test that a min aspect ratio activity eligible for size compat mode does not have the
216      * Display APIs sandboxed when the 'never_constrain_display_apis_all_packages' Device Config
217      * flag is true.
218      */
219     @Test
testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue()220     public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue() {
221         setNeverConstrainDisplayApisAllPackagesFlag("true");
222         // Setting 'never_constrain_display_apis' as well to make sure it is ignored.
223         setNeverConstrainDisplayApisFlag("com.android.other::");
224         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
225                 /* isSandboxed= */ false);
226     }
227 
228     /**
229      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
230      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
231      * package with an open ended range.
232      */
233     @Test
testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag()234     public void testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag() {
235         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
236         setNeverConstrainDisplayApisFlag(
237                 "com.android.other::," + activity.getPackageName() + "::");
238         runSizeCompatModeSandboxTest(activity, /* isSandboxed= */ false);
239     }
240 
241     /**
242      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
243      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
244      * package with a version range that matches the installed version of the package.
245      */
246     @Test
testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig()247     public void testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig() {
248         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
249         long version = getPackageVersion(activity);
250         setNeverConstrainDisplayApisFlag(
251                 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
252                         version - 1) + ":" + String.valueOf(version + 1));
253         runSizeCompatModeSandboxTest(activity, /* isSandboxed= */ false);
254     }
255 
256     /**
257      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
258      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
259      * package with a version range that doesn't match the installed version of the package.
260      */
261     @Test
testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig()262     public void testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig() {
263         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
264         long version = getPackageVersion(activity);
265         setNeverConstrainDisplayApisFlag(
266                 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
267                         version + 1) + ":");
268         runSizeCompatModeSandboxTest(activity, /* isSandboxed= */ true);
269     }
270 
271     /**
272      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
273      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag doesn't contain the
274      * test package.
275      */
276     @Test
testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag()277     public void testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag() {
278         setNeverConstrainDisplayApisFlag("com.android.other::,com.android.other2::");
279         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
280                 /* isSandboxed= */ true);
281     }
282 
283     /**
284      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
285      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag is empty.
286      */
287     @Test
testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty()288     public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty() {
289         setNeverConstrainDisplayApisFlag("");
290         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
291                 /* isSandboxed= */ true);
292     }
293 
294     /**
295      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
296      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains an invalid
297      * entry for the test package.
298      */
299     @Test
testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag()300     public void testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag() {
301         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
302         setNeverConstrainDisplayApisFlag(
303                 "com.android.other::," + activity.getPackageName() + ":::");
304         runSizeCompatModeSandboxTest(activity, /* isSandboxed= */ true);
305     }
306 
307     // =================
308     // ALWAYS_SANDBOX test cases
309     // =================
310     // Validates that an activity simply in letterbox mode has sandboxing applied to the max
311     // bounds when ALWAYS_SANDBOX is set. Without the flag, we would not expect a letterbox activity
312     // to be sandboxed, unless it is also eligible for size compat mode.
313 
314     /**
315      * Test that a portrait activity not eligible for size compat mode does have the
316      * Display APIs sandboxed when the {@link ActivityInfo#ALWAYS_SANDBOX_DISPLAY_APIS} compat
317      * change is enabled.
318      */
319     @Test
320     @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled()321     public void testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled() {
322         runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed= */ true);
323     }
324 
325     /**
326      * Test that a portrait activity not eligible for size compat mode does not have the
327      * Display APIs sandboxed when the 'always_constrain_display_apis' Device Config flag is empty.
328      */
329     @Test
testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty()330     public void testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty() {
331         setAlwaysConstrainDisplayApisFlag("");
332         runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed= */ false);
333     }
334 
335     /**
336      * Test that a portrait activity not eligible for size compat mode does have the Display
337      * APIs sandboxed when the 'always_constrain_display_apis' Device Config flag contains the test
338      * package.
339      */
340     @Test
testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag()341     public void testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag() {
342         ComponentName activity = RESIZEABLE_PORTRAIT_ACTIVITY;
343         setAlwaysConstrainDisplayApisFlag(
344                 "com.android.other::," + activity.getPackageName() + "::");
345         runLetterboxSandboxTest(activity, /* isSandboxed= */ true);
346     }
347 
348     /**
349      * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO} has no effect on its
350      * own. The aspect ratio of the activity should be the same as that of the task, which should be
351      * in line with that of the display.
352      */
353     @Test
354     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
testOverrideMinAspectRatioMissingSpecificOverride()355     public void testOverrideMinAspectRatioMissingSpecificOverride() {
356         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected= */ 0);
357     }
358 
359     /**
360      * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
361      * its own without the presence of {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO}.
362      */
363     @Test
364     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioMissingGeneralOverride()365     public void testOverrideMinAspectRatioMissingGeneralOverride() {
366         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected= */ 0);
367     }
368 
369     /**
370      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
371      * activities whose orientation is fixed to landscape.
372      */
373     @Test
374     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
375             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioForLandscapeActivity()376     public void testOverrideMinAspectRatioForLandscapeActivity() {
377         runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected= */ 0);
378     }
379 
380     /**
381      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
382      * activities whose orientation isn't fixed.
383      */
384     @Test
385     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
386             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
387     @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled()388     public void testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled() {
389         runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected= */
390                 OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
391     }
392 
393     /**
394      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
395      * activities whose orientation is fixed to landscape.
396      */
397     @Test
398     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
399             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
400     @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled()401     public void testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled() {
402         runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected= */
403                 OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
404     }
405 
406     /**
407      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
408      * activities whose orientation isn't fixed.
409      */
410     @Test
411     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
412             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioForNonFixedOrientationActivity()413     public void testOverrideMinAspectRatioForNonFixedOrientationActivity() {
414         runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected= */ 0);
415     }
416 
417     /**
418      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} sets the min aspect
419      * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE}.
420      */
421     @Test
422     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
423             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioLargeAspectRatio()424     public void testOverrideMinAspectRatioLargeAspectRatio() {
425         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
426                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
427     }
428 
429     /**
430      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM} sets the min aspect
431      * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE}.
432      */
433     @Test
434     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
435             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioMediumAspectRatio()436     public void testOverrideMinAspectRatioMediumAspectRatio() {
437         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
438                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
439     }
440 
441     /**
442      * Test that applying multiple min aspect ratio overrides result in the largest one taking
443      * effect.
444      */
445     @Test
446     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
447             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
448             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioBothAspectRatios()449     public void testOverrideMinAspectRatioBothAspectRatios() {
450         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
451                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
452     }
453 
454     /**
455      * Test that the min aspect ratio of the activity as defined in the manifest is ignored if
456      * there is an override for a larger min aspect ratio present (16:9 > 1.6).
457      */
458     @Test
459     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
460             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride()461     public void testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride() {
462         runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
463                 /* expected= */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
464     }
465 
466     /**
467      * Test that the min aspect ratio of the activity as defined in the manifest is upheld if
468      * there is an override for a smaller min aspect ratio present (3:2 < 1.6).
469      */
470     @Test
471     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
472             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride()473     public void testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride() {
474         runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
475                 /* expected= */ ACTIVITY_MIN_ASPECT_RATIO);
476     }
477 
478     /**
479      * Launches the provided activity into size compat mode twice. The first time, the display
480      * is resized to be half the size. The second time, the display is resized to be twice the
481      * original size.
482      *
483      * @param activity                    the activity under test.
484      * @param windowingMode               the launch windowing mode for the activity
485      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
486      *                                    resizing the display
487      */
runSizeCompatTest(ComponentName activity, int windowingMode, boolean inSizeCompatModeAfterResize)488     private void runSizeCompatTest(ComponentName activity, int windowingMode,
489             boolean inSizeCompatModeAfterResize) {
490         runSizeCompatTest(activity, windowingMode, /* resizeRatio= */ 0.5,
491                 inSizeCompatModeAfterResize);
492         restoreDisplay(activity);
493         runSizeCompatTest(activity, windowingMode, /* resizeRatio= */ 2,
494                 inSizeCompatModeAfterResize);
495     }
496 
497     /**
498      * Launches the provided activity on the default display, initially not in size compat mode.
499      * After resizing the display, verifies if activity is in size compat mode or not
500      *
501      * @param activity                    the activity under test
502      * @param windowingMode               the launch windowing mode for the activity
503      * @param resizeRatio                 the ratio to resize the display
504      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
505      *                                    resizing the display
506      */
runSizeCompatTest(ComponentName activity, int windowingMode, double resizeRatio, boolean inSizeCompatModeAfterResize)507     private void runSizeCompatTest(ComponentName activity, int windowingMode, double resizeRatio,
508             boolean inSizeCompatModeAfterResize) {
509         // TODO(b/208918131): Remove once real cause is found.
510         assumeFalse(ENABLE_SHELL_TRANSITIONS);
511 
512         launchActivity(activity, windowingMode);
513 
514         assertSizeCompatMode(activity, /* expectedInSizeCompatMode= */ false);
515 
516         resizeDisplay(activity, resizeRatio);
517 
518         assertSizeCompatMode(activity, inSizeCompatModeAfterResize);
519     }
520 
assertSizeCompatMode(ComponentName activity, boolean expectedInSizeCompatMode)521     private void assertSizeCompatMode(ComponentName activity, boolean expectedInSizeCompatMode) {
522         WindowManagerState.Activity activityContainer = mWmState.getActivity(activity);
523         assertNotNull(activityContainer);
524         if (expectedInSizeCompatMode) {
525             assertTrue("The Window must be in size compat mode",
526                     activityContainer.inSizeCompatMode());
527         } else {
528             assertFalse("The Window must not be in size compat mode",
529                     activityContainer.inSizeCompatMode());
530         }
531     }
532 
runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed)533     private void runSizeCompatModeSandboxTest(ComponentName activity,
534             boolean isSandboxed) {
535         runSizeCompatModeSandboxTest(activity, isSandboxed,
536                 /* inSizeCompatModeAfterResize= */ true);
537     }
538 
539     /**
540      * Similar to {@link #runSizeCompatTest(ComponentName, int, boolean)}, but the activity is
541      * expected to be in size compat mode after resizing the display.
542      *
543      * @param activity                    the activity under test
544      * @param isSandboxed                 when {@code true},
545      * {@link android.app.WindowConfiguration#getMaxBounds()}
546      *                                    are sandboxed to the activity bounds. Otherwise, they
547      *                                    inherit the
548      *                                    DisplayArea bounds
549      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
550      *                                    resizing the display
551      */
runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed, boolean inSizeCompatModeAfterResize)552     private void runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed,
553             boolean inSizeCompatModeAfterResize) {
554         assertThat(getInitialDisplayAspectRatio()).isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
555         runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio= */ 0.5,
556                 inSizeCompatModeAfterResize);
557         assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
558         restoreDisplay(activity);
559         runSizeCompatTest(activity, WINDOWING_MODE_FULLSCREEN, /* resizeRatio= */ 2,
560                 inSizeCompatModeAfterResize);
561         assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
562     }
563 
564     /**
565      * Similar to {@link #runSizeCompatModeSandboxTest(ComponentName, boolean)}, but the
566      * activity is put into letterbox mode after resizing the display.
567      *
568      * @param activityName the activity under test
569      * @param isSandboxed  when {@code true}, {@link android.app.WindowConfiguration#getMaxBounds()}
570      *                     are sandboxed to the activity bounds. Otherwise, they inherit the
571      *                     DisplayArea bounds
572      */
runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed)573     private void runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed) {
574         assertThat(getInitialDisplayAspectRatio()).isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
575         // Initialize display to portrait orientation.
576         final RotationSession rotationSession = createManagedRotationSession();
577         Size originalDisplaySize = mDisplayMetricsSession.getInitialDisplayMetrics().getSize();
578         if (originalDisplaySize.getHeight() < originalDisplaySize.getWidth()) {
579             // Device is landscape
580             rotationSession.set(ROTATION_90);
581         } else if (originalDisplaySize.getHeight() == originalDisplaySize.getWidth()) {
582             // Device is square, so skip this test case (portrait activity will never be
583             // letterboxed)
584             return;
585         }
586 
587         // Launch portrait activity
588         launchActivity(activityName, WINDOWING_MODE_FULLSCREEN);
589 
590         // Change display to landscape should force portrait resizeable activity into letterbox.
591         changeDisplayAspectRatioAndWait(activityName, /* aspectRatio= */ 2);
592         assertSandboxedByProvidesMaxBounds(activityName, isSandboxed);
593     }
594 
assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed)595     private void assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed) {
596         mWmState.computeState(new WaitForValidActivityState(activityName));
597         final WindowManagerState.Activity activity = mWmState.getActivity(activityName);
598         assertNotNull(activity);
599         final Rect activityBounds = activity.getBounds();
600         final Rect maxBounds = activity.getMaxBounds();
601         WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
602         assertNotNull(tda);
603         if (isSandboxed) {
604             assertEquals(
605                     "The window has max bounds sandboxed to the window bounds",
606                     activityBounds, maxBounds);
607         } else {
608             assertEquals(
609                     "The window is not sandboxed, with max bounds reflecting the DisplayArea",
610                     tda.getBounds(), maxBounds);
611         }
612     }
613 
assertSandboxedByProvidesMaxBounds(ComponentName activityName, boolean isSandboxed)614     private void assertSandboxedByProvidesMaxBounds(ComponentName activityName, boolean isSandboxed) {
615         mWmState.computeState(new WaitForValidActivityState(activityName));
616         final WindowManagerState.Activity activity = mWmState.getActivity(activityName);
617         assertNotNull(activity);
618         if (isSandboxed) {
619             assertTrue(
620                     "The window should have max bounds sandboxed to the window bounds",
621                     activity.providesMaxBounds());
622         } else {
623             assertFalse(
624                     "The window should not be sandboxed; max bounds should reflect the DisplayArea",
625                     activity.providesMaxBounds());
626         }
627     }
628 
629     private class ConstrainDisplayApisFlagsSession implements AutoCloseable {
630         private Properties mInitialProperties;
631 
ConstrainDisplayApisFlagsSession()632         ConstrainDisplayApisFlagsSession() {
633             runWithShellPermission(
634                     () -> {
635                         mInitialProperties = DeviceConfig.getProperties(
636                                 NAMESPACE_CONSTRAIN_DISPLAY_APIS);
637                         try {
638                             DeviceConfig.setProperties(new Properties.Builder(
639                                     NAMESPACE_CONSTRAIN_DISPLAY_APIS).build());
640                         } catch (Exception e) {
641                         }
642                     });
643         }
644 
645         @Override
close()646         public void close() {
647             runWithShellPermission(
648                     () -> {
649                         try {
650                             DeviceConfig.setProperties(mInitialProperties);
651                         } catch (Exception e) {
652                         }
653                     });
654         }
655     }
656 
657     /** @see ObjectTracker#manage(AutoCloseable) */
createManagedConstrainDisplayApisFlagsSession()658     private ConstrainDisplayApisFlagsSession createManagedConstrainDisplayApisFlagsSession() {
659         return mObjectTracker.manage(new ConstrainDisplayApisFlagsSession());
660     }
661 
setNeverConstrainDisplayApisFlag(@ullable String value)662     private void setNeverConstrainDisplayApisFlag(@Nullable String value) {
663         setConstrainDisplayApisFlag("never_constrain_display_apis", value);
664     }
665 
setNeverConstrainDisplayApisAllPackagesFlag(@ullable String value)666     private void setNeverConstrainDisplayApisAllPackagesFlag(@Nullable String value) {
667         setConstrainDisplayApisFlag("never_constrain_display_apis_all_packages", value);
668     }
669 
setAlwaysConstrainDisplayApisFlag(@ullable String value)670     private void setAlwaysConstrainDisplayApisFlag(@Nullable String value) {
671         setConstrainDisplayApisFlag("always_constrain_display_apis", value);
672     }
673 
setConstrainDisplayApisFlag(String flagName, @Nullable String value)674     private void setConstrainDisplayApisFlag(String flagName, @Nullable String value) {
675         runWithShellPermission(
676                 () -> {
677                     DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS, flagName,
678                             value, /* makeDefault= */ false);
679                 });
680     }
681 
682     /**
683      * Launches the provided activity and verifies that its min aspect ratio is equal to {@code
684      * expected}.
685      *
686      * @param activity the activity under test.
687      * @param expected the expected min aspect ratio in both portrait and landscape displays.
688      */
runMinAspectRatioTest(ComponentName activity, float expected)689     private void runMinAspectRatioTest(ComponentName activity, float expected) {
690         launchActivity(activity);
691         WindowManagerState.Activity activityContainer = mWmState.getActivity(activity);
692         assertNotNull(activityContainer);
693         assertEquals(expected,
694                 activityContainer.getMinAspectRatio(),
695                 FLOAT_EQUALITY_DELTA);
696     }
697 
698     /**
699      * Restore the display size and ensure configuration changes are complete.
700      */
restoreDisplay(ComponentName activity)701     private void restoreDisplay(ComponentName activity) {
702         final Rect originalBounds = mWmState.getActivity(activity).getBounds();
703         mDisplayMetricsSession.restoreDisplayMetrics();
704         // Ensure configuration changes are complete after resizing the display.
705         waitForActivityBoundsChanged(activity, originalBounds);
706     }
707 
708     /**
709      * Resize the display and ensure configuration changes are complete.
710      */
resizeDisplay(ComponentName activity, double sizeRatio)711     private void resizeDisplay(ComponentName activity, double sizeRatio) {
712         Size originalDisplaySize = mDisplayMetricsSession.getInitialDisplayMetrics().getSize();
713         final Rect originalBounds = mWmState.getActivity(activity).getBounds();
714         mDisplayMetricsSession.changeDisplayMetrics(sizeRatio, /* densityRatio= */ 1);
715         mWmState.computeState(new WaitForValidActivityState(activity));
716 
717         Size currentDisplaySize = mDisplayMetricsSession.getDisplayMetrics().getSize();
718         assumeFalse("If a display size is capped, resizing may be a no-op",
719                 originalDisplaySize.equals(currentDisplaySize));
720 
721         // Ensure configuration changes are complete after resizing the display.
722         waitForActivityBoundsChanged(activity, originalBounds);
723     }
724 
725     /**
726      * Resize the display to given aspect ratio in landscape orientation, and ensure configuration
727      * changes are complete.
728      */
changeDisplayAspectRatioAndWait(ComponentName activity, double aspectRatio)729     private void changeDisplayAspectRatioAndWait(ComponentName activity, double aspectRatio) {
730         mWmState.computeState(new WaitForValidActivityState(activity));
731         Size originalDisplaySize = mDisplayMetricsSession.getInitialDisplayMetrics().getSize();
732         final Rect originalBounds = mWmState.getActivity(activity).getBounds();
733         mDisplayMetricsSession.changeAspectRatio(aspectRatio,
734                 /* orientation= */ ORIENTATION_LANDSCAPE);
735         mWmState.computeState(new WaitForValidActivityState(activity));
736 
737         Size currentDisplaySize = mDisplayMetricsSession.getDisplayMetrics().getSize();
738         assumeFalse("If a display size is capped, resizing may be a no-op",
739                 originalDisplaySize.equals(currentDisplaySize));
740 
741         // Ensure configuration changes are complete after resizing the display.
742         waitForActivityBoundsChanged(activity, originalBounds);
743     }
744 
745     /**
746      * Waits until the given activity has updated task bounds.
747      */
waitForActivityBoundsChanged(ComponentName activityName, Rect priorActivityBounds)748     private void waitForActivityBoundsChanged(ComponentName activityName, Rect priorActivityBounds) {
749         mWmState.waitForWithAmState(wmState -> {
750             WindowManagerState.Activity activity = wmState.getActivity(activityName);
751             return activity != null && !activity.getBounds().equals(priorActivityBounds);
752         }, "checking activity bounds updated");
753     }
754 
getInitialDisplayAspectRatio()755     private float getInitialDisplayAspectRatio() {
756         Size size = mDisplayMetricsSession.getInitialDisplayMetrics().getSize();
757         return Math.max(size.getHeight(), size.getWidth())
758                 / (float) (Math.min(size.getHeight(), size.getWidth()));
759     }
760 
launchActivity(ComponentName activity)761     private void launchActivity(ComponentName activity) {
762         launchActivity(activity, WINDOWING_MODE_FULLSCREEN);
763     }
764 
launchActivity(ComponentName activity, int windowingMode)765     private void launchActivity(ComponentName activity, int windowingMode) {
766         getLaunchActivityBuilder()
767                 .setDisplayId(DEFAULT_DISPLAY)
768                 .setTargetActivity(activity)
769                 .setWindowingMode(windowingMode)
770                 .setUseInstrumentation()
771                 .allowMultipleInstances(false)
772                 .execute();
773     }
774 
getPackageVersion(ComponentName activity)775     private long getPackageVersion(ComponentName activity) {
776         try {
777             return mContext.getPackageManager().getPackageInfo(activity.getPackageName(),
778                     /* flags= */ 0).getLongVersionCode();
779         } catch (PackageManager.NameNotFoundException e) {
780             throw new RuntimeException(e);
781         }
782     }
783 
component(Class<? extends Activity> activity)784     private static ComponentName component(Class<? extends Activity> activity) {
785         return new ComponentName(getInstrumentation().getContext(), activity);
786     }
787 
788     public static class ResizeablePortraitActivity extends FocusableActivity {
789     }
790 
791     public static class NonResizeablePortraitActivity extends FocusableActivity {
792     }
793 
794     public static class NonResizeableLandscapeActivity extends FocusableActivity {
795     }
796 
797     public static class NonResizeableNonFixedOrientationActivity extends FocusableActivity {
798     }
799 
800     public static class NonResizeableAspectRatioActivity extends FocusableActivity {
801     }
802 
803     public static class NonResizeableLargeAspectRatioActivity extends FocusableActivity {
804     }
805 
806     public static class SupportsSizeChangesPortraitActivity extends FocusableActivity {
807     }
808 }
809