• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_ENABLE_COMPAT_FAKE_FOCUS;
21 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
22 import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
23 import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
24 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
25 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
26 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
27 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
28 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
29 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
30 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
31 import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
32 import static android.server.wm.allowdisplayorientationoverride.Components.ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY;
33 import static android.server.wm.alloworientationoverride.Components.ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY;
34 import static android.server.wm.alloworientationoverride.Components.ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY;
35 import static android.server.wm.allowsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED;
36 import static android.server.wm.allowsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED;
37 import static android.server.wm.allowsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY;
38 import static android.server.wm.allowsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS;
39 import static android.server.wm.enablefakefocusoptin.Components.ENABLE_FAKE_FOCUS_OPT_IN_LEFT_ACTIVITY;
40 import static android.server.wm.enablefakefocusoptin.Components.ENABLE_FAKE_FOCUS_OPT_IN_RIGHT_ACTIVITY;
41 import static android.server.wm.enablefakefocusoptout.Components.ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY;
42 import static android.server.wm.enablefakefocusoptout.Components.ENABLE_FAKE_FOCUS_OPT_OUT_RIGHT_ACTIVITY;
43 import static android.server.wm.ignorerequestedorientationoverrideoptin.Components.OPT_IN_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY;
44 import static android.server.wm.ignorerequestedorientationoverrideoptout.Components.OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY;
45 import static android.server.wm.optoutsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED;
46 import static android.server.wm.optoutsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY;
47 import static android.server.wm.optoutsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_OPT_OUT_TIMEOUT_MS;
48 import static android.server.wm.propertycameracompatallowforcerotation.Components.CAMERA_COMPAT_ALLOW_FORCE_ROTATION_ACTIVITY;
49 import static android.server.wm.propertycameracompatallowrefresh.Components.CAMERA_COMPAT_ALLOW_REFRESH_ACTIVITY;
50 import static android.server.wm.propertycameracompatenablerefreshviapauseoptin.Components.CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_IN_ACTIVITY;
51 import static android.server.wm.propertycameracompatenablerefreshviapauseoptout.Components.CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY;
52 import static android.view.Surface.ROTATION_0;
53 import static android.view.Surface.ROTATION_90;
54 
55 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
56 
57 import static com.google.common.truth.Truth.assertThat;
58 
59 import static org.junit.Assert.assertEquals;
60 import static org.junit.Assert.assertFalse;
61 import static org.junit.Assert.assertNotNull;
62 import static org.junit.Assert.assertTrue;
63 import static org.junit.Assume.assumeTrue;
64 
65 import android.app.Activity;
66 import android.app.WindowConfiguration;
67 import android.compat.testing.PlatformCompatChangeRule;
68 import android.content.BroadcastReceiver;
69 import android.content.ComponentName;
70 import android.content.Context;
71 import android.content.Intent;
72 import android.content.IntentFilter;
73 import android.content.pm.ActivityInfo;
74 import android.content.pm.PackageManager;
75 import android.content.res.Resources;
76 import android.graphics.Rect;
77 import android.os.Bundle;
78 import android.os.ConditionVariable;
79 import android.platform.test.annotations.Presubmit;
80 import android.provider.DeviceConfig;
81 import android.server.wm.app.AbstractLifecycleLogActivity;
82 import android.util.Size;
83 
84 import androidx.annotation.NonNull;
85 import androidx.annotation.Nullable;
86 
87 import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
88 import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
89 
90 import org.junit.Before;
91 import org.junit.Rule;
92 import org.junit.Test;
93 import org.junit.rules.TestRule;
94 
95 import java.util.Collections;
96 import java.util.HashMap;
97 import java.util.Map;
98 
99 /**
100  * The test is focused on compatibility changes that have an effect on WM logic, and tests that
101  * enabling these changes has the correct effect.
102  *
103  * This is achieved by launching a custom activity with certain properties (e.g., a resizeable
104  * portrait activity) that behaves in a certain way (e.g., enter size compat mode after resizing the
105  * display) and enabling a compatibility change (e.g., {@link ActivityInfo#FORCE_RESIZE_APP}) that
106  * changes that behavior (e.g., not enter size compat mode).
107  *
108  * The behavior without enabling a compatibility change is also tested as a baseline.
109  *
110  * <p>Build/Install/Run:
111  * atest CtsWindowManagerDeviceTestCases:CompatChangeTests
112  */
113 @Presubmit
114 public final class CompatChangeTests extends MultiDisplayTestBase {
115     private static final ComponentName RESIZEABLE_PORTRAIT_ACTIVITY =
116             component(ResizeablePortraitActivity.class);
117     private static final ComponentName NON_RESIZEABLE_PORTRAIT_ACTIVITY =
118             component(NonResizeablePortraitActivity.class);
119     private static final ComponentName NON_RESIZEABLE_LANDSCAPE_ACTIVITY =
120             component(NonResizeableLandscapeActivity.class);
121     private static final ComponentName NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY =
122             component(NonResizeableNonFixedOrientationActivity.class);
123     private static final ComponentName NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY =
124             component(NonResizeableAspectRatioActivity.class);
125     private static final ComponentName NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY =
126             component(NonResizeableLargeAspectRatioActivity.class);
127     private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY =
128             component(SupportsSizeChangesPortraitActivity.class);
129     private static final ComponentName RESIZEABLE_LEFT_ACTIVITY =
130             component(ResizeableLeftActivity.class);
131     private static final ComponentName RESIZEABLE_RIGHT_ACTIVITY =
132             component(ResizeableRightActivity.class);
133     private static final ComponentName RESPONSIVE_ACTIVITY =
134             component(ResponsiveActivity.class);
135     private static final ComponentName NO_PROPERTY_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY =
136             component(NoPropertyChangeOrientationWhileRelaunchingActivity.class);
137 
138     // Fixed orientation min aspect ratio
139     private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f;
140     // The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest).
141     private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f;
142     // The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the
143     // manifest). This needs to be higher than the aspect ratio of any device, which according to
144     // CDD is at most 21:9.
145     private static final float ACTIVITY_LARGE_MIN_ASPECT_RATIO = 4f;
146 
147     private static final float FLOAT_EQUALITY_DELTA = 0.01f;
148 
149     @Rule
150     public TestRule compatChangeRule = new PlatformCompatChangeRule();
151 
152     @Before
153     @Override
setUp()154     public void setUp() throws Exception {
155         super.setUp();
156         enableAndAssumeGestureNavigationMode();
157         createManagedLetterboxAspectRatioSession(FIXED_ORIENTATION_MIN_ASPECT_RATIO);
158         mObjectTracker.manage(setAlwaysConstrainDisplayApisFlag(null));
159         mObjectTracker.manage(setNeverConstrainDisplayApisAllPackagesFlag(null));
160         mObjectTracker.manage(setNeverConstrainDisplayApisFlag(null));
161     }
162 
163     @Test
testOverrideUndefinedOrientationToPortrait_propertyIsFalse_overrideNotApplied()164     public void testOverrideUndefinedOrientationToPortrait_propertyIsFalse_overrideNotApplied() {
165         try (var compatChange = new CompatChangeCloseable(
166                 ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
167                 ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY.getPackageName());
168              var session = new ActivitySessionCloseable(
169                      ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY)) {
170             waitAssertEquals("expected unspecified orientation",
171                     SCREEN_ORIENTATION_UNSPECIFIED,
172                     () -> session.getActivityState().getOverrideOrientation());
173         }
174     }
175 
176     @Test
177     @EnableCompatChanges({ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
testOverrideUndefinedOrientationToPortrait()178     public void testOverrideUndefinedOrientationToPortrait() {
179         try (var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) {
180             waitAssertEquals("expected portrait orientation", SCREEN_ORIENTATION_PORTRAIT,
181                     () -> session.getActivityState().getOverrideOrientation());
182         }
183     }
184 
185     @Test
testOverrideUndefinedOrientationToNoSensor_propertyIsFalse_overrideNotApplied()186     public void testOverrideUndefinedOrientationToNoSensor_propertyIsFalse_overrideNotApplied() {
187         try (var compatChange = new CompatChangeCloseable(
188                 ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR,
189                 ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY.getPackageName());
190             var session = new ActivitySessionCloseable(
191                     ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY)) {
192             waitAssertEquals("expected unspecified orientation",
193                     SCREEN_ORIENTATION_UNSPECIFIED,
194                     () -> session.getActivityState().getOverrideOrientation());
195         }
196     }
197 
198     @Test
199     @EnableCompatChanges({ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
testOverrideUndefinedOrientationToNosensor()200     public void testOverrideUndefinedOrientationToNosensor() {
201         try (var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) {
202             waitAssertEquals("expected no-sensor orientation",
203                     SCREEN_ORIENTATION_NOSENSOR,
204                     () -> session.getActivityState().getOverrideOrientation());
205         }
206     }
207 
208     @Test
209     public void
testOverrideLandscapeOrientationToReverseLandscape_propertyIsFalse_overrideNotApply()210             testOverrideLandscapeOrientationToReverseLandscape_propertyIsFalse_overrideNotApply() {
211         try (var compatChange = new CompatChangeCloseable(
212                 ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
213                 ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY.getPackageName());
214              var session = new ActivitySessionCloseable(
215                      ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY)) {
216             waitAssertEquals("expected landscape orientation", SCREEN_ORIENTATION_LANDSCAPE,
217                     () -> session.getActivityState().getOverrideOrientation());
218         }
219     }
220 
221     @Test
222     @EnableCompatChanges({ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
testOverrideLandscapeOrientationToReverseLandscape()223     public void testOverrideLandscapeOrientationToReverseLandscape() {
224         try (var session = new ActivitySessionCloseable(NON_RESIZEABLE_LANDSCAPE_ACTIVITY)) {
225             waitAssertEquals("expected reverse landscape orientation",
226                     SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
227                     () -> session.getActivityState().getOverrideOrientation());
228         }
229     }
230 
231     @Test
232     @EnableCompatChanges({ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
testOverrideUseDisplayLandscapeNaturalOrientation()233     public void testOverrideUseDisplayLandscapeNaturalOrientation() {
234         try (var displayMetricsSession = new DisplayMetricsWaitCloseable()) {
235             // Run this test only when natural orientation is landscape
236             Size displaySize = displayMetricsSession.getInitialDisplayMetrics().getSize();
237             assumeTrue(displaySize.getHeight() < displaySize.getWidth());
238         }
239 
240         try (var portrait = new DeviceOrientationCloseable(ORIENTATION_PORTRAIT);
241              var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) {
242             // Verifying that orientation is overridden
243             waitAssertEquals("expected rotation 0",
244                     ROTATION_0, () -> mWmState.getRotation());
245         }
246     }
247 
248     @Test
249     public void
testOverrideUseDisplayLandscapeNaturalOrientation_propertyIsFalse_overrideNotApplied()250             testOverrideUseDisplayLandscapeNaturalOrientation_propertyIsFalse_overrideNotApplied() {
251         try (var displayMetricsSession = new DisplayMetricsWaitCloseable()) {
252             // Run this test only when natural orientation is landscape
253             Size displaySize = displayMetricsSession.getInitialDisplayMetrics().getSize();
254             assumeTrue(displaySize.getHeight() < displaySize.getWidth());
255         }
256 
257         final int originalRotation = mWmState.getRotation();
258 
259         try (var portrait = new DeviceOrientationCloseable(ORIENTATION_PORTRAIT);
260              var compatChange = new CompatChangeCloseable(
261                 ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION,
262                 ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY.getPackageName());
263              var session = new ActivitySessionCloseable(
264                     ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY)) {
265 
266             // Verifying that orientation not overridden
267             if (portrait.isRotationApplied()) {
268                 // If the screen was rotated away from natural orientation (to portrait)
269                 waitAssertEquals("expected rotation 90",
270                         ROTATION_90, () -> mWmState.getRotation());
271             } else {
272                 // If the screen was already in portrait (rotated away from natural orientation)
273                 waitAssertEquals("expected originalRotation=" + originalRotation,
274                         originalRotation, () -> mWmState.getRotation());
275             }
276         }
277     }
278 
279     @Test
testEnableFakeFocus_propertyIsFalse_overrideNotApplied()280     public void testEnableFakeFocus_propertyIsFalse_overrideNotApplied() {
281         assumeTrue("Skipping test: no split multi-window support",
282                 supportsSplitScreenMultiWindow());
283         assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled",
284                 getFakeFocusEnabledConfig());
285 
286         try (var compatChange = new CompatChangeCloseable(
287                 OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS,
288                 ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY.getPackageName());
289              var splitScreen = new SplitScreenActivitiesCloseable(
290                      ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY,
291                      ENABLE_FAKE_FOCUS_OPT_OUT_RIGHT_ACTIVITY)) {
292             waitAssertEquals("expected should not send compat fake focus",
293                     /* expected */ false,
294                     () -> splitScreen.getPrimaryActivity()
295                             .getActivityState().getShouldSendCompatFakeFocus());
296         }
297     }
298 
299     @Test
testEnableFakeFocus_propertyIsTrue_returnsTrue()300     public void testEnableFakeFocus_propertyIsTrue_returnsTrue() {
301         assumeTrue("Skipping test: no split multi-window support",
302                 supportsSplitScreenMultiWindow());
303         assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled",
304                 getFakeFocusEnabledConfig());
305 
306         try (var splitScreen = new SplitScreenActivitiesCloseable(
307                      ENABLE_FAKE_FOCUS_OPT_IN_LEFT_ACTIVITY,
308                      ENABLE_FAKE_FOCUS_OPT_IN_RIGHT_ACTIVITY)) {
309             waitAssertEquals("expected should send compat fake focus", /* expected */ true,
310                     () -> splitScreen.getPrimaryActivity()
311                             .getActivityState().getShouldSendCompatFakeFocus());
312         }
313     }
314 
315     @Test
316     @EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
testEnableFakeFocus_overrideApplied_returnsTrue()317     public void testEnableFakeFocus_overrideApplied_returnsTrue() {
318         assumeTrue("Skipping test: no split multi-window support",
319                 supportsSplitScreenMultiWindow());
320         assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled",
321                 getFakeFocusEnabledConfig());
322 
323         try (var splitScreen = new SplitScreenActivitiesCloseable(
324                      RESIZEABLE_LEFT_ACTIVITY,
325                      RESIZEABLE_RIGHT_ACTIVITY)) {
326             waitAssertEquals("expected should send compat fake focus", /* expected */ true,
327                     () -> splitScreen.getPrimaryActivity()
328                             .getActivityState().getShouldSendCompatFakeFocus());
329         }
330     }
331 
getFakeFocusEnabledConfig()332     boolean getFakeFocusEnabledConfig() {
333         return mContext.getResources().getBoolean(
334                 Resources.getSystem().getIdentifier(
335                         "config_isCompatFakeFocusEnabled",
336                         "bool", "android"));
337     }
338 
339     @Test
testOverrideIgnoreRequestedOrientation_propertyIsFalse_overrideNotApplied()340     public void testOverrideIgnoreRequestedOrientation_propertyIsFalse_overrideNotApplied() {
341         assumeTrue("Skipping test: "
342                     + "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled",
343                 isPolicyForIgnoringRequestedOrientationEnabled());
344 
345         try (var compatChange = new CompatChangeCloseable(
346                     ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION,
347                     OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY.getPackageName());
348              var session = new ActivitySessionCloseable(
349                      OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) {
350             waitAssertEquals("expected landscape orientation",
351                     SCREEN_ORIENTATION_LANDSCAPE,
352                     () -> session.getActivityState().getOverrideOrientation());
353         }
354     }
355 
356     @Test
testOverrideIgnoreRequestedOrientation_isDisabled_propertyIsTrue_overrideApplied()357     public void testOverrideIgnoreRequestedOrientation_isDisabled_propertyIsTrue_overrideApplied() {
358         assumeTrue("Skipping test: "
359                     + "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled",
360                 isPolicyForIgnoringRequestedOrientationEnabled());
361 
362         try (var session = new ActivitySessionCloseable(
363                      OPT_IN_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) {
364             waitAssertEquals("expected portrait orientation",
365                     SCREEN_ORIENTATION_PORTRAIT,
366                     () -> session.getActivityState().getOverrideOrientation());
367         }
368     }
369 
370     @Test
371     @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
testOverrideIgnoreRequestedOrientation()372     public void testOverrideIgnoreRequestedOrientation() {
373         assumeTrue("Skipping test: "
374                     + "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled",
375                 isPolicyForIgnoringRequestedOrientationEnabled());
376 
377         try (var session = new ActivitySessionCloseable(
378                      NO_PROPERTY_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) {
379             waitAssertEquals("expected portrait orientation",
380                     SCREEN_ORIENTATION_PORTRAIT,
381                     () -> session.getActivityState().getOverrideOrientation());
382         }
383     }
384 
385     @Test
testOptOutPropertyCameraCompatForceRotation_rotationDisabled()386     public void testOptOutPropertyCameraCompatForceRotation_rotationDisabled() {
387         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
388                 isCameraCompatForceRotationTreatmentConfigEnabled());
389 
390         try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
391             // Activity without property or override is eligible for force rotation.
392             waitAssertEquals("expected to force rotate for camera compat",
393                     /* expected */ true,
394                     () -> session.getActivityState().getShouldForceRotateForCameraCompat());
395         }
396 
397         try (var session = new ActivitySessionCloseable(
398                      CAMERA_COMPAT_ALLOW_FORCE_ROTATION_ACTIVITY)) {
399             waitAssertEquals("expected to not force rotate for camera compat",
400                     /* expected */ false,
401                     () -> session.getActivityState().getShouldForceRotateForCameraCompat());
402         }
403     }
404 
405     @Test
406     @EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
testOverrideForCameraCompatForceRotation_rotationDisabled()407     public void testOverrideForCameraCompatForceRotation_rotationDisabled() {
408         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
409                 isCameraCompatForceRotationTreatmentConfigEnabled());
410 
411         try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
412             waitAssertEquals("expected to not force rotate for camera compat",
413                     /* expected */ false,
414                     () -> session.getActivityState().getShouldForceRotateForCameraCompat());
415         }
416     }
417 
418     @Test
testOptOutPropertyCameraCompatRefresh()419     public void testOptOutPropertyCameraCompatRefresh() {
420         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
421                 isCameraCompatForceRotationTreatmentConfigEnabled());
422 
423         try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
424             // Activity without property or override is eligible for refresh.
425             waitAssertEquals("expected to refresh activity for camera compat",
426                     /* expected */ true,
427                     () -> session.getActivityState().getShouldRefreshActivityForCameraCompat());
428         }
429 
430         try (var session = new ActivitySessionCloseable(CAMERA_COMPAT_ALLOW_REFRESH_ACTIVITY)) {
431             waitAssertEquals("expected to not refresh activity for camera compat",
432                     /* expected */ false,
433                     () -> session.getActivityState().getShouldRefreshActivityForCameraCompat());
434         }
435     }
436 
437     @Test
438     @EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
testOverrideForCameraCompatRefresh()439     public void testOverrideForCameraCompatRefresh() {
440         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
441                 isCameraCompatForceRotationTreatmentConfigEnabled());
442 
443         try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
444             waitAssertEquals("expected to not refresh activity for camera compat",
445                     /* expected */ false,
446                     () -> session.getActivityState().getShouldRefreshActivityForCameraCompat());
447         }
448     }
449 
450     @Test
testOptInPropertyCameraCompatRefreshViaPause()451     public void testOptInPropertyCameraCompatRefreshViaPause() {
452         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
453                 isCameraCompatForceRotationTreatmentConfigEnabled());
454 
455         try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
456             // Activity without property or override doesn't refresh via
457             // "resumed -> paused -> resumed".
458             waitAssertEquals("expected to not refresh activity via pause for camera compat",
459                     /* expected */ false,
460                     () -> session.getActivityState()
461                             .getShouldRefreshActivityViaPauseForCameraCompat());
462         }
463 
464         try (var session = new ActivitySessionCloseable(
465                 CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_IN_ACTIVITY)) {
466             waitAssertEquals("expected to Refresh activity via pause for camera compat",
467                     /* expected */ true,
468                     () -> session.getActivityState()
469                             .getShouldRefreshActivityViaPauseForCameraCompat());
470         }
471     }
472 
473     @Test
474     @EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
testOverrideForCameraCompatRefreshViaPause()475     public void testOverrideForCameraCompatRefreshViaPause() {
476         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
477                 isCameraCompatForceRotationTreatmentConfigEnabled());
478 
479         try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
480             waitAssertEquals("expected to Refresh activity via pause for camera compat",
481                     /* expected */ true,
482                     () -> session.getActivityState()
483                             .getShouldRefreshActivityViaPauseForCameraCompat());
484         }
485     }
486 
487     @Test
testOptOutPropertyCameraCompatRefreshViaPause()488     public void testOptOutPropertyCameraCompatRefreshViaPause() {
489         assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
490                 isCameraCompatForceRotationTreatmentConfigEnabled());
491 
492         try (var compatChange = new CompatChangeCloseable(
493                 ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
494                 CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY.getPackageName());
495              var session = new ActivitySessionCloseable(
496                      CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY)) {
497             waitAssertEquals("expected to not refresh activity via pause for camera compat",
498                     /* expected */ false,
499                     () -> session.getActivityState()
500                             .getShouldRefreshActivityViaPauseForCameraCompat());
501         }
502     }
503 
504     /**
505      * Test that a non-resizeable portrait activity enters size compat mode after resizing the
506      * display.
507      */
508     @Test
testSizeCompatForNonResizeableActivity()509     public void testSizeCompatForNonResizeableActivity() {
510         runSizeCompatTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true);
511     }
512 
513     /**
514      * Test that a non-resizeable portrait activity doesn't enter size compat mode after resizing
515      * the display, when the {@link ActivityInfo#FORCE_RESIZE_APP} compat change is enabled.
516      */
517     @Test
518     @EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
testSizeCompatForNonResizeableActivityForceResizeEnabled()519     public void testSizeCompatForNonResizeableActivityForceResizeEnabled() {
520         runSizeCompatTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /*inSizeCompatModeAfterResize */ false);
521     }
522 
523     /**
524      * Test that a resizeable portrait activity doesn't enter size compat mode after resizing
525      * the display.
526      */
527     @Test
testSizeCompatForResizeableActivity()528     public void testSizeCompatForResizeableActivity() {
529         runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ false);
530     }
531 
532     /**
533      * Test that a non-resizeable portrait activity that supports size changes doesn't enter size
534      * compat mode after resizing the display.
535      */
536     @Test
testSizeCompatForSupportsSizeChangesActivity()537     public void testSizeCompatForSupportsSizeChangesActivity() {
538         runSizeCompatTest(
539                 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ false);
540     }
541 
542     /**
543      * Test that a resizeable portrait activity enters size compat mode after resizing
544      * the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat change is enabled.
545      */
546     @Test
547     @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
testSizeCompatForResizeableActivityForceNonResizeEnabled()548     public void testSizeCompatForResizeableActivityForceNonResizeEnabled() {
549         runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true);
550     }
551 
552     /**
553      * Test that a non-resizeable portrait activity that supports size changes enters size compat
554      * mode after resizing the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat
555      * change is enabled.
556      */
557     @Test
558     @EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled()559     public void testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled() {
560         runSizeCompatTest(
561                 SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true);
562     }
563 
564     /**
565      * Test that a min aspect ratio activity eligible for size compat mode results in sandboxed
566      * Display APIs.
567      */
568     @Test
testSandboxForNonResizableAspectRatioActivity()569     public void testSandboxForNonResizableAspectRatioActivity() {
570         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
571                 /* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true);
572     }
573 
574      // =================
575      // NEVER_SANDBOX test cases
576      // =================
577      // Validates that an activity forced into size compat mode never has sandboxing applied to the
578      // max bounds. It is expected that an activity in size compat mode normally always has
579      // sandboxing applied.
580 
581     /**
582      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
583      * APIs sandboxed when the {@link ActivityInfo#NEVER_SANDBOX_DISPLAY_APIS} compat change is
584      * enabled.
585      */
586     @Test
587     @EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled()588     public void testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled() {
589         runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
590                 /* isSandboxed */ false, /* inSizeCompatModeAfterResize */ true);
591     }
592 
593     /**
594      * Test that a min aspect ratio activity eligible for size compat mode does not have the
595      * Display APIs sandboxed when the 'never_constrain_display_apis_all_packages' Device Config
596      * flag is true.
597      */
598     @Test
testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue()599     public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue() {
600         try (var neverForAll = setNeverConstrainDisplayApisAllPackagesFlag("true");
601                 var neverAnApp = setNeverConstrainDisplayApisFlag("com.android.other::")) {
602             runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
603                     /* isSandboxed */ false, /* inSizeCompatModeAfterResize */ true);
604         }
605     }
606 
607     /**
608      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
609      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
610      * package with an open ended range.
611      */
612     @Test
testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag()613     public void testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag() {
614         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
615         try (var neverForApp = setNeverConstrainDisplayApisFlag(
616                 "com.android.other::," + activity.getPackageName() + "::")) {
617             runSizeCompatModeSandboxTest(activity, /* isSandboxed */ false,
618                     /* inSizeCompatModeAfterResize */ true);
619         }
620     }
621 
622     /**
623      * Test that a min aspect ratio activity eligible for size compat mode does not have the Display
624      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
625      * package with a version range that matches the installed version of the package.
626      */
627     @Test
testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig()628     public void testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig() {
629         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
630         long version = getPackageVersion(activity);
631         try (var neverForApp = setNeverConstrainDisplayApisFlag(
632                 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
633                         version - 1) + ":" + String.valueOf(version + 1))) {
634             runSizeCompatModeSandboxTest(activity, /* isSandboxed */ false,
635                     /* inSizeCompatModeAfterResize */ true);
636         }
637     }
638 
639     /**
640      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
641      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
642      * package with a version range that doesn't match the installed version of the package.
643      */
644     @Test
testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig()645     public void testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig() {
646         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
647         long version = getPackageVersion(activity);
648         try (var neverForApp = setNeverConstrainDisplayApisFlag(
649                 "com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
650                         version + 1) + ":")) {
651             runSizeCompatModeSandboxTest(activity, /* isSandboxed */ true,
652                     /* inSizeCompatModeAfterResize */ true);
653         }
654     }
655 
656     /**
657      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
658      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag doesn't contain the
659      * test package.
660      */
661     @Test
testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag()662     public void testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag() {
663         try (var neverForApp = setNeverConstrainDisplayApisFlag(
664                 "com.android.other::,com.android.other2::")) {
665             runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
666                     /* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true);
667         }
668     }
669 
670     /**
671      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
672      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag is empty.
673      */
674     @Test
testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty()675     public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty() {
676         try (var empty = setNeverConstrainDisplayApisFlag("")) {
677             runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
678                     /* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true);
679         }
680     }
681 
682     /**
683      * Test that a min aspect ratio activity eligible for size compat mode does have the Display
684      * APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains an invalid
685      * entry for the test package.
686      */
687     @Test
testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag()688     public void testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag() {
689         ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
690         try (var neverForApp = setNeverConstrainDisplayApisFlag(
691                 "com.android.other::," + activity.getPackageName() + ":::")) {
692             runSizeCompatModeSandboxTest(activity, /* isSandboxed */ true,
693                     /* inSizeCompatModeAfterResize */ true);
694         }
695     }
696 
697     /** =================
698      * SANDBOX_VIEW_BOUNDS_APIS test cases
699      * @see #testSandbox_viewApiForLetterboxedActivity
700      * @see #testNoSandbox_viewApiForLetterboxedActivity
701      * @see #testNoSandbox_viewApiForLetterboxedActivityOptOut
702      * =================
703      * Validates that an activity in letterbox mode has sandboxing applied to the
704      * view bounds when OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is set.
705      * Without this flag or with
706      * {@link android.view.WindowManager#PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS}
707      * value=false in AndroidManifest.xml
708      * {@link android.view.View#getLocationOnScreen},
709      * {@link android.view.View#getWindowDisplayFrame}
710      * {@link android.view.View#getBoundsOnScreen}
711      * and {@link android.view.View#getWindowVisibleDisplayFrame}
712      * return location or display frame offset by the window location on the screen:
713      * {@link WindowConfiguration#getBounds}
714      */
715     @Test
testSandbox_viewApiForLetterboxedActivity()716     public void testSandbox_viewApiForLetterboxedActivity() {
717         // Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application
718         try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f);
719              var compatChange = new CompatChangeCloseable(
720                 OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS,
721                 TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY.getPackageName());
722              var receiver = new BroadcastReceiverCloseable(mContext,
723                      ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED)) {
724 
725             try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY)) {
726                 // Wait for the broadcast action
727                 boolean testPassed = receiver
728                         .getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED)
729                         .block(TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS);
730 
731                 assertThat(testPassed).isTrue();
732             }
733         }
734     }
735 
736     @Test
testNoSandbox_viewApiForLetterboxedActivity()737     public void testNoSandbox_viewApiForLetterboxedActivity() {
738         // Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application
739         try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f);
740              var receiver = new BroadcastReceiverCloseable(mContext,
741                      ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED)) {
742 
743             try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY)) {
744                 // Wait for the broadcast action
745                 boolean testPassed = receiver
746                         .getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED)
747                         .block(TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS);
748 
749                 assertThat(testPassed).isTrue();
750             }
751         }
752     }
753 
754     @Test
testNoSandbox_viewApiForLetterboxedActivityOptOut()755     public void testNoSandbox_viewApiForLetterboxedActivityOptOut() {
756         // Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application
757         try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f);
758              var compatChange = new CompatChangeCloseable(
759                 OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS,
760                 TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY.getPackageName());
761              var receiver = new BroadcastReceiverCloseable(mContext,
762                      ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED)) {
763 
764             try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY)) {
765                 // Wait for the broadcast action
766                 boolean testPassed = receiver
767                         .getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED)
768                         .block(TEST_VIEW_SANDBOX_OPT_OUT_TIMEOUT_MS);
769 
770                 assertThat(testPassed).isTrue();
771             }
772         }
773     }
774 
775     // =================
776     // ALWAYS_SANDBOX test cases
777     // =================
778     // Validates that an activity simply in letterbox mode has sandboxing applied to the max
779     // bounds when ALWAYS_SANDBOX is set. Without the flag, we would not expect a letterbox activity
780     // to be sandboxed, unless it is also eligible for size compat mode.
781 
782     /**
783      * Test that a portrait activity not eligible for size compat mode does have the
784      * Display APIs sandboxed when the {@link ActivityInfo#ALWAYS_SANDBOX_DISPLAY_APIS} compat
785      * change is enabled.
786      */
787     @Test
788     @EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled()789     public void testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled() {
790         runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed */ true);
791     }
792 
793     /**
794      * Test that a portrait activity not eligible for size compat mode does not have the
795      * Display APIs sandboxed when the 'always_constrain_display_apis' Device Config flag is empty.
796      */
797     @Test
testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty()798     public void testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty() {
799         runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed */ false);
800     }
801 
802     /**
803      * Test that a portrait activity not eligible for size compat mode does have the Display
804      * APIs sandboxed when the 'always_constrain_display_apis' Device Config flag contains the test
805      * package.
806      */
807     @Test
testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag()808     public void testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag() {
809         ComponentName activity = RESIZEABLE_PORTRAIT_ACTIVITY;
810         try (var alwaysForApp = setAlwaysConstrainDisplayApisFlag(
811                 "com.android.other::," + activity.getPackageName() + "::")) {
812             runLetterboxSandboxTest(activity, /* isSandboxed */ true);
813         }
814     }
815 
816     /**
817      * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO} has no effect on its
818      * own. The aspect ratio of the activity should be the same as that of the task, which should be
819      * in line with that of the display.
820      */
821     @Test
822     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
testOverrideMinAspectRatioMissingSpecificOverride()823     public void testOverrideMinAspectRatioMissingSpecificOverride() {
824         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected */ 0);
825     }
826 
827     /**
828      * Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
829      * its own without the presence of {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO}.
830      */
831     @Test
832     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioMissingGeneralOverride()833     public void testOverrideMinAspectRatioMissingGeneralOverride() {
834         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected */ 0);
835     }
836 
837     /**
838      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
839      * activities whose orientation is fixed to landscape.
840      */
841     @Test
842     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
843             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioForLandscapeActivity()844     public void testOverrideMinAspectRatioForLandscapeActivity() {
845         runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected */ 0);
846     }
847 
848     /**
849      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
850      * activities whose orientation isn't fixed.
851      */
852     @Test
853     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
854             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
855     @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled()856     public void testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled() {
857         runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected */
858                 OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
859     }
860 
861     /**
862      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
863      * activities whose orientation is fixed to landscape.
864      */
865     @Test
866     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
867             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
868     @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled()869     public void testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled() {
870         runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected */
871                 OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
872     }
873 
874     /**
875      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
876      * activities whose orientation isn't fixed.
877      */
878     @Test
879     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
880             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioForNonFixedOrientationActivity()881     public void testOverrideMinAspectRatioForNonFixedOrientationActivity() {
882         runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected */ 0);
883     }
884 
885     /**
886      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} sets the min aspect
887      * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE}.
888      */
889     @Test
890     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
891             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioLargeAspectRatio()892     public void testOverrideMinAspectRatioLargeAspectRatio() {
893         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
894                 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
895     }
896 
897     /**
898      * Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM} sets the min aspect
899      * ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE}.
900      */
901     @Test
902     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
903             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioMediumAspectRatio()904     public void testOverrideMinAspectRatioMediumAspectRatio() {
905         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
906                 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
907     }
908 
909     /**
910      * Test that applying multiple min aspect ratio overrides result in the largest one taking
911      * effect.
912      */
913     @Test
914     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
915             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
916             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioBothAspectRatios()917     public void testOverrideMinAspectRatioBothAspectRatios() {
918         runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
919                 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
920     }
921 
922     /**
923      * Test that the min aspect ratio of the activity as defined in the manifest is ignored if
924      * there is an override for a larger min aspect ratio present (16:9 > 1.6).
925      */
926     @Test
927     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
928             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride()929     public void testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride() {
930         runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
931                 /* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
932     }
933 
934     /**
935      * Test that the min aspect ratio of the activity as defined in the manifest is upheld if
936      * there is an override for a smaller min aspect ratio present (3:2 < 1.6).
937      */
938     @Test
939     @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
940             ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride()941     public void testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride() {
942         runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
943                 /* expected */ ACTIVITY_MIN_ASPECT_RATIO);
944     }
945 
isCameraCompatForceRotationTreatmentConfigEnabled()946     private boolean isCameraCompatForceRotationTreatmentConfigEnabled() {
947         return getBooleanConfig("config_isWindowManagerCameraCompatTreatmentEnabled");
948     }
949 
isPolicyForIgnoringRequestedOrientationEnabled()950     private boolean isPolicyForIgnoringRequestedOrientationEnabled() {
951         return getBooleanConfig("config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled");
952     }
953 
getBooleanConfig(String configName)954     private boolean getBooleanConfig(String configName) {
955         return mContext.getResources().getBoolean(
956                 Resources.getSystem().getIdentifier(configName, "bool", "android"));
957     }
958 
959     /**
960      * Launches the provided activity into size compat mode twice. The first time, the display
961      * is resized to be half the size. The second time, the display is resized to be twice the
962      * original size.
963      *
964      * @param activity                    the activity under test.
965      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
966      *                                    resizing the display
967      */
runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize)968     private void runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize) {
969         try (var session = new ActivitySessionCloseable(activity)) {
970             runSizeCompatTestForActivity(activity, /* resizeRatio */ 0.5,
971                     inSizeCompatModeAfterResize);
972         }
973 
974         try (var session = new ActivitySessionCloseable(activity)) {
975             runSizeCompatTestForActivity(activity, /* resizeRatio */ 2,
976                     inSizeCompatModeAfterResize);
977         }
978     }
979 
980     /**
981      * Launches the provided activity on the default display, initially not in size compat mode.
982      * After resizing the display, verifies if activity is in size compat mode or not
983      *
984      * @param activity                    the activity under test
985      * @param resizeRatio                 the ratio to resize the display
986      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
987      *                                    resizing the display
988      */
runSizeCompatTestForActivity(ComponentName activity, final double resizeRatio, final boolean inSizeCompatModeAfterResize)989     private void runSizeCompatTestForActivity(ComponentName activity, final double resizeRatio,
990             final boolean inSizeCompatModeAfterResize) {
991 
992         waitAndAssertSizeCompatMode(activity, /* expectedInSizeCompatMode */ false);
993 
994         try (var resized = new DisplaySizeScaleCloseable(resizeRatio, activity)) {
995             assertThat(resized.getInitialDisplayAspectRatio())
996                     .isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
997             waitAndAssertSizeCompatMode(activity, inSizeCompatModeAfterResize);
998         }
999     }
1000 
waitAndAssertSizeCompatMode(final ComponentName activity, final boolean expectedInSizeCompatMode)1001     private void waitAndAssertSizeCompatMode(final ComponentName activity,
1002             final boolean expectedInSizeCompatMode) {
1003         waitAssertEquals("The window must be inSizeCompatMode==" + expectedInSizeCompatMode,
1004                 expectedInSizeCompatMode,
1005                 () -> getActivityWaitState(activity).inSizeCompatMode());
1006     }
1007 
1008     /**
1009      * Similar to {@link #runSizeCompatTest(ComponentName, boolean)}, but the activity is
1010      * expected to be in size compat mode after resizing the display.
1011      *
1012      * @param activity                    the activity under test
1013      * @param isSandboxed                 when {@code true},
1014      * {@link android.app.WindowConfiguration#getMaxBounds()}
1015      *                                    are sandboxed to the activity bounds. Otherwise, they
1016      *                                    inherit the
1017      *                                    DisplayArea bounds
1018      * @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
1019      *                                    resizing the display
1020      */
runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed, boolean inSizeCompatModeAfterResize)1021     private void runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed,
1022             boolean inSizeCompatModeAfterResize) {
1023         try (var session = new ActivitySessionCloseable(activity, WINDOWING_MODE_FULLSCREEN)) {
1024             runSizeCompatTestForActivity(activity, /* resizeRatio */ 0.5,
1025                     inSizeCompatModeAfterResize);
1026             assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
1027             assertSandboxedByBounds(activity, isSandboxed);
1028         }
1029 
1030         try (var session = new ActivitySessionCloseable(activity, WINDOWING_MODE_FULLSCREEN)) {
1031             runSizeCompatTestForActivity(activity, /* resizeRatio */ 2,
1032                     inSizeCompatModeAfterResize);
1033             assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
1034             assertSandboxedByBounds(activity, isSandboxed);
1035         }
1036     }
1037 
1038     /**
1039      * Similar to {@link #runSizeCompatModeSandboxTest(ComponentName, boolean, boolean)}, but the
1040      * activity is put into letterbox mode after resizing the display.
1041      *
1042      * @param activityName the activity under test
1043      * @param isSandboxed  when {@code true}, {@link android.app.WindowConfiguration#getMaxBounds()}
1044      *                     are sandboxed to the activity bounds. Otherwise, they inherit the
1045      *                     DisplayArea bounds
1046      */
runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed)1047     private void runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed) {
1048         try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f)) {
1049             assertThat(aspectRatio.getInitialDisplayAspectRatio())
1050                     .isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
1051 
1052             try (var session = new ActivitySessionCloseable(activityName)) {
1053                 assertSandboxedByProvidesMaxBounds(activityName, isSandboxed);
1054             }
1055         }
1056     }
1057 
assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed)1058     private void assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed) {
1059         final WindowManagerState.Activity activity = getActivityWaitState(activityName);
1060         assertNotNull(activity);
1061         final Rect activityBounds = activity.getBounds();
1062         final Rect maxBounds = activity.getMaxBounds();
1063         WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
1064         assertNotNull(tda);
1065         if (isSandboxed) {
1066             assertEquals(
1067                     "The window has max bounds sandboxed to the window bounds",
1068                     activityBounds, maxBounds);
1069         } else {
1070             assertEquals(
1071                     "The window is not sandboxed, with max bounds reflecting the DisplayArea",
1072                     tda.getBounds(), maxBounds);
1073         }
1074     }
1075 
assertSandboxedByProvidesMaxBounds(ComponentName activityName, boolean isSandboxed)1076     private void assertSandboxedByProvidesMaxBounds(ComponentName activityName,
1077             boolean isSandboxed) {
1078         final WindowManagerState.Activity activity = getActivityWaitState(activityName);
1079         assertNotNull(activity);
1080         if (isSandboxed) {
1081             assertTrue(
1082                     "The window should have max bounds sandboxed to the window bounds",
1083                     activity.providesMaxBounds());
1084         } else {
1085             assertFalse(
1086                     "The window should not be sandboxed; max bounds should reflect the DisplayArea",
1087                     activity.providesMaxBounds());
1088         }
1089     }
1090 
setNeverConstrainDisplayApisFlag(@ullable String value)1091     private CloseableDeviceConfig setNeverConstrainDisplayApisFlag(@Nullable String value) {
1092         return setConstrainDisplayApisFlag("never_constrain_display_apis", value);
1093     }
1094 
setNeverConstrainDisplayApisAllPackagesFlag( @ullable String value)1095     private CloseableDeviceConfig setNeverConstrainDisplayApisAllPackagesFlag(
1096             @Nullable String value) {
1097         return setConstrainDisplayApisFlag("never_constrain_display_apis_all_packages", value);
1098     }
1099 
setAlwaysConstrainDisplayApisFlag( @ullable String value)1100     private CloseableDeviceConfig setAlwaysConstrainDisplayApisFlag(
1101             @Nullable String value) {
1102         return setConstrainDisplayApisFlag("always_constrain_display_apis", value);
1103     }
1104 
setConstrainDisplayApisFlag(@onNull String flagName, @Nullable String value)1105     private CloseableDeviceConfig setConstrainDisplayApisFlag(@NonNull String flagName,
1106             @Nullable String value) {
1107         return new CloseableDeviceConfig(NAMESPACE_CONSTRAIN_DISPLAY_APIS, flagName, value);
1108     }
1109 
1110     /**
1111      * Launches the provided activity and verifies that its min aspect ratio is equal to {@code
1112      * expected}.
1113      *
1114      * @param activity the activity under test.
1115      * @param expected the expected min aspect ratio in both portrait and landscape displays.
1116      */
runMinAspectRatioTest(ComponentName activity, float expected)1117     private void runMinAspectRatioTest(ComponentName activity, float expected) {
1118         try (var session = new ActivitySessionCloseable(activity)) {
1119             assertNotNull(session.getActivityState());
1120             assertEquals(expected,
1121                     session.getActivityState().getMinAspectRatio(),
1122                     FLOAT_EQUALITY_DELTA);
1123         }
1124     }
1125 
getPackageVersion(ComponentName activity)1126     private long getPackageVersion(ComponentName activity) {
1127         try {
1128             return mContext.getPackageManager().getPackageInfo(activity.getPackageName(),
1129                     /* flags */ 0).getLongVersionCode();
1130         } catch (PackageManager.NameNotFoundException e) {
1131             throw new RuntimeException(e);
1132         }
1133     }
1134 
component(Class<? extends Activity> activity)1135     private static ComponentName component(Class<? extends Activity> activity) {
1136         return new ComponentName(getInstrumentation().getContext(), activity);
1137     }
1138 
1139     public static class ResizeablePortraitActivity extends AbstractLifecycleLogActivity {
1140     }
1141 
1142     public static class ResponsiveActivity extends AbstractLifecycleLogActivity {
1143     }
1144 
1145     public static class NonResizeablePortraitActivity extends AbstractLifecycleLogActivity {
1146     }
1147 
1148     public static class NonResizeableLandscapeActivity extends AbstractLifecycleLogActivity {
1149     }
1150 
1151     public static class NonResizeableNonFixedOrientationActivity extends
1152             AbstractLifecycleLogActivity {
1153     }
1154 
1155     public static class NonResizeableAspectRatioActivity extends AbstractLifecycleLogActivity {
1156     }
1157 
1158     public static class NonResizeableLargeAspectRatioActivity extends AbstractLifecycleLogActivity {
1159     }
1160 
1161     public static class SupportsSizeChangesPortraitActivity extends AbstractLifecycleLogActivity {
1162     }
1163 
1164     public static class ResizeableLeftActivity extends AbstractLifecycleLogActivity {
1165     }
1166 
1167     public static class ResizeableRightActivity extends AbstractLifecycleLogActivity {
1168     }
1169 
1170     public static class NoPropertyChangeOrientationWhileRelaunchingActivity extends
1171             AbstractLifecycleLogActivity {
1172 
1173         private static boolean sHasChangeOrientationInOnResume;
1174 
1175         @Override
onCreate(Bundle instance)1176         protected void onCreate(Bundle instance) {
1177             super.onCreate(instance);
1178             // When OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION is enabled this request
1179             // should be ignored if sHasChangeOrientationInOnResume is true.
1180             setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
1181         }
1182 
1183         @Override
onResume()1184         protected void onResume() {
1185             super.onResume();
1186             if (!sHasChangeOrientationInOnResume) {
1187                 setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
1188                 sHasChangeOrientationInOnResume = true;
1189             }
1190         }
1191     }
1192 
1193     /**
1194      * Registers broadcast receiver which receives result actions from Activities under test.
1195      */
1196     private static class BroadcastReceiverCloseable implements AutoCloseable {
1197         private final Context mContext;
1198         private final Map<String, ConditionVariable> mBroadcastsReceived;
1199         private final BroadcastReceiver mAppCommunicator = new BroadcastReceiver() {
1200             @Override
1201             public void onReceive(Context context, Intent intent) {
1202                 getBroadcastReceivedVariable(intent.getAction()).open();
1203             }
1204         };
1205 
BroadcastReceiverCloseable(final Context context, final String action)1206         BroadcastReceiverCloseable(final Context context, final String action) {
1207             this.mContext = context;
1208             // Keep the received broadcast items in the map.
1209             mBroadcastsReceived = Collections.synchronizedMap(new HashMap<>());
1210             // Register for broadcast actions.
1211             IntentFilter filter = new IntentFilter();
1212             filter.addAction(action);
1213             mContext.registerReceiver(mAppCommunicator, filter, Context.RECEIVER_EXPORTED);
1214         }
1215 
getBroadcastReceivedVariable(String action)1216         ConditionVariable getBroadcastReceivedVariable(String action) {
1217             return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable());
1218         }
1219 
1220         @Override
close()1221         public void close() {
1222             mContext.unregisterReceiver(mAppCommunicator);
1223         }
1224     }
1225 
1226     /**
1227      * Resets device config to the original value after the try-with-resources block finishes
1228      * try (var dc = new CloseableDeviceConfig()) {...}
1229      */
1230     private class CloseableDeviceConfig implements AutoCloseable {
1231         private String mOriginalValue;
1232         private final String mNamespace;
1233         private final String mFlagName;
1234 
CloseableDeviceConfig(@onNull String namespace, @NonNull String flagName, @Nullable String value)1235         CloseableDeviceConfig(@NonNull String namespace, @NonNull String flagName,
1236                 @Nullable String value) {
1237             mNamespace = namespace;
1238             mFlagName = flagName;
1239             runWithShellPermission(() -> {
1240                 mOriginalValue = DeviceConfig.getProperty(namespace, flagName);
1241                 DeviceConfig.setProperty(namespace, flagName, value, /* makeDefault */
1242                         false);
1243             });
1244         }
1245 
1246         @Override
close()1247         public void close() {
1248             runWithShellPermission(() -> DeviceConfig.setProperty(mNamespace, mFlagName,
1249                     mOriginalValue, /* makeDefault */ false));
1250         }
1251     }
1252 }
1253