blob: 92a238fca23b2134a97ad58f4e22a97bf477c96d [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.server.wm.display;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
import static android.content.pm.ActivityInfo.OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.provider.DeviceConfig.NAMESPACE_CONSTRAIN_DISPLAY_APIS;
import static android.server.wm.allowdisplayorientationoverride.Components.ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY;
import static android.server.wm.alloworientationoverride.Components.ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY;
import static android.server.wm.alloworientationoverride.Components.ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY;
import static android.server.wm.allowsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED;
import static android.server.wm.allowsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED;
import static android.server.wm.allowsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY;
import static android.server.wm.allowsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS;
import static android.server.wm.enablefakefocusoptin.Components.ENABLE_FAKE_FOCUS_OPT_IN_LEFT_ACTIVITY;
import static android.server.wm.enablefakefocusoptin.Components.ENABLE_FAKE_FOCUS_OPT_IN_RIGHT_ACTIVITY;
import static android.server.wm.enablefakefocusoptout.Components.ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY;
import static android.server.wm.enablefakefocusoptout.Components.ENABLE_FAKE_FOCUS_OPT_OUT_RIGHT_ACTIVITY;
import static android.server.wm.ignorerequestedorientationoverrideoptin.Components.OPT_IN_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY;
import static android.server.wm.ignorerequestedorientationoverrideoptout.Components.OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY;
import static android.server.wm.optoutsandboxingviewboundsapis.Components.ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED;
import static android.server.wm.optoutsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY;
import static android.server.wm.optoutsandboxingviewboundsapis.Components.TEST_VIEW_SANDBOX_OPT_OUT_TIMEOUT_MS;
import static android.server.wm.propertycameracompatallowforcerotation.Components.CAMERA_COMPAT_ALLOW_FORCE_ROTATION_ACTIVITY;
import static android.server.wm.propertycameracompatallowrefresh.Components.CAMERA_COMPAT_ALLOW_REFRESH_ACTIVITY;
import static android.server.wm.propertycameracompatenablerefreshviapauseoptin.Components.CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_IN_ACTIVITY;
import static android.server.wm.propertycameracompatenablerefreshviapauseoptout.Components.CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.WindowConfiguration;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
import android.server.wm.HelperActivities;
import android.server.wm.MultiDisplayTestBase;
import android.server.wm.WindowManagerState;
import android.server.wm.app.AbstractLifecycleLogActivity;
import android.util.Size;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* The test is focused on compatibility changes that have an effect on WM logic, and tests that
* enabling these changes has the correct effect.
*
* This is achieved by launching a custom activity with certain properties (e.g., a resizeable
* portrait activity) that behaves in a certain way (e.g., enter size compat mode after resizing the
* display) and enabling a compatibility change (e.g., {@link ActivityInfo#FORCE_RESIZE_APP}) that
* changes that behavior (e.g., not enter size compat mode).
*
* The behavior without enabling a compatibility change is also tested as a baseline.
*
* <p>Build/Install/Run:
* atest CtsWindowManagerDeviceDisplay:CompatChangeTests
*/
@Presubmit
public final class CompatChangeTests extends MultiDisplayTestBase {
private static final ComponentName RESIZEABLE_PORTRAIT_ACTIVITY =
component(HelperActivities.ResizeablePortraitActivity.class);
private static final ComponentName NON_RESIZEABLE_PORTRAIT_ACTIVITY =
component(HelperActivities.NonResizeablePortraitActivity.class);
private static final ComponentName NON_RESIZEABLE_LANDSCAPE_ACTIVITY =
component(HelperActivities.NonResizeableLandscapeActivity.class);
private static final ComponentName NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY =
component(HelperActivities.NonResizeableNonFixedOrientationActivity.class);
private static final ComponentName NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY =
component(HelperActivities.NonResizeableAspectRatioActivity.class);
private static final ComponentName NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY =
component(HelperActivities.NonResizeableLargeAspectRatioActivity.class);
private static final ComponentName SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY =
component(HelperActivities.SupportsSizeChangesPortraitActivity.class);
private static final ComponentName RESIZEABLE_LEFT_ACTIVITY =
component(HelperActivities.ResizeableLeftActivity.class);
private static final ComponentName RESIZEABLE_RIGHT_ACTIVITY =
component(HelperActivities.ResizeableRightActivity.class);
private static final ComponentName RESPONSIVE_ACTIVITY =
component(HelperActivities.ResponsiveActivity.class);
private static final ComponentName NO_PROPERTY_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY =
component(HelperActivities.NoPropertyChangeOrientationWhileRelaunchingActivity.class);
// Fixed orientation min aspect ratio
private static final float FIXED_ORIENTATION_MIN_ASPECT_RATIO = 1.03f;
// The min aspect ratio of NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY (as defined in the manifest).
private static final float ACTIVITY_MIN_ASPECT_RATIO = 1.6f;
// The min aspect ratio of NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY (as defined in the
// manifest). This needs to be higher than the aspect ratio of any device, which according to
// CDD is at most 21:9.
private static final float ACTIVITY_LARGE_MIN_ASPECT_RATIO = 4f;
private static final float FLOAT_EQUALITY_DELTA = 0.01f;
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@Before
@Override
public void setUp() throws Exception {
super.setUp();
enableAndAssumeGestureNavigationMode();
createManagedLetterboxAspectRatioSession(FIXED_ORIENTATION_MIN_ASPECT_RATIO);
mObjectTracker.manage(setAlwaysConstrainDisplayApisFlag(null));
mObjectTracker.manage(setNeverConstrainDisplayApisAllPackagesFlag(null));
mObjectTracker.manage(setNeverConstrainDisplayApisFlag(null));
}
@Test
public void testOverrideUndefinedOrientationToPortrait_propertyIsFalse_overrideNotApplied() {
try (var compatChange = new CompatChangeCloseable(
ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT,
ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY.getPackageName());
var session = new ActivitySessionCloseable(
ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY)) {
waitAssertEquals("expected unspecified orientation",
SCREEN_ORIENTATION_UNSPECIFIED,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT})
public void testOverrideUndefinedOrientationToPortrait() {
try (var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) {
waitAssertEquals("expected portrait orientation", SCREEN_ORIENTATION_PORTRAIT,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
public void testOverrideUndefinedOrientationToNoSensor_propertyIsFalse_overrideNotApplied() {
try (var compatChange = new CompatChangeCloseable(
ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR,
ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY.getPackageName());
var session = new ActivitySessionCloseable(
ALLOW_ORIENTATION_OVERRIDE_RESPONSIVE_ACTIVITY)) {
waitAssertEquals("expected unspecified orientation",
SCREEN_ORIENTATION_UNSPECIFIED,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR})
public void testOverrideUndefinedOrientationToNosensor() {
try (var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) {
waitAssertEquals("expected no-sensor orientation",
SCREEN_ORIENTATION_NOSENSOR,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
public void
testOverrideLandscapeOrientationToReverseLandscape_propertyIsFalse_overrideNotApply() {
try (var compatChange = new CompatChangeCloseable(
ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE,
ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY.getPackageName());
var session = new ActivitySessionCloseable(
ALLOW_ORIENTATION_OVERRIDE_LANDSCAPE_ACTIVITY)) {
waitAssertEquals("expected landscape orientation", SCREEN_ORIENTATION_LANDSCAPE,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE})
public void testOverrideLandscapeOrientationToReverseLandscape() {
try (var session = new ActivitySessionCloseable(NON_RESIZEABLE_LANDSCAPE_ACTIVITY)) {
waitAssertEquals("expected reverse landscape orientation",
SCREEN_ORIENTATION_REVERSE_LANDSCAPE,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION})
public void testOverrideUseDisplayLandscapeNaturalOrientation() {
try (var displayMetricsSession = new DisplayMetricsWaitCloseable()) {
// Run this test only when natural orientation is landscape
Size displaySize = displayMetricsSession.getInitialDisplayMetrics().getSize();
assumeTrue(displaySize.getHeight() < displaySize.getWidth());
}
try (var portrait = new DeviceOrientationCloseable(ORIENTATION_PORTRAIT);
var session = new ActivitySessionCloseable(RESPONSIVE_ACTIVITY)) {
// Verifying that orientation is overridden
waitAssertEquals("expected rotation 0",
ROTATION_0, () -> mWmState.getRotation());
}
}
@Test
public void
testOverrideUseDisplayLandscapeNaturalOrientation_propertyIsFalse_overrideNotApplied() {
try (var displayMetricsSession = new DisplayMetricsWaitCloseable()) {
// Run this test only when natural orientation is landscape
Size displaySize = displayMetricsSession.getInitialDisplayMetrics().getSize();
assumeTrue(displaySize.getHeight() < displaySize.getWidth());
}
final int originalRotation = mWmState.getRotation();
try (var portrait = new DeviceOrientationCloseable(ORIENTATION_PORTRAIT);
var compatChange = new CompatChangeCloseable(
ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION,
ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY.getPackageName());
var session = new ActivitySessionCloseable(
ALLOW_DISPLAY_ORIENTATION_OVERRIDE_ACTIVITY)) {
// Verifying that orientation not overridden
if (portrait.isRotationApplied()) {
// If the screen was rotated away from natural orientation (to portrait)
waitAssertEquals("expected rotation 90",
ROTATION_90, () -> mWmState.getRotation());
} else {
// If the screen was already in portrait (rotated away from natural orientation)
waitAssertEquals("expected originalRotation=" + originalRotation,
originalRotation, () -> mWmState.getRotation());
}
}
}
@Test
public void testEnableFakeFocus_propertyIsFalse_overrideNotApplied() {
assumeTrue("Skipping test: no split multi-window support",
supportsSplitScreenMultiWindow());
assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled",
getFakeFocusEnabledConfig());
try (var compatChange = new CompatChangeCloseable(
OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS,
ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY.getPackageName());
var splitScreen = new SplitScreenActivitiesCloseable(
ENABLE_FAKE_FOCUS_OPT_OUT_LEFT_ACTIVITY,
ENABLE_FAKE_FOCUS_OPT_OUT_RIGHT_ACTIVITY)) {
waitAssertEquals("expected should not send compat fake focus",
/* expected */ false,
() -> splitScreen.getPrimaryActivity()
.getActivityState().getShouldSendCompatFakeFocus());
}
}
@Test
public void testEnableFakeFocus_propertyIsTrue_returnsTrue() {
assumeTrue("Skipping test: no split multi-window support",
supportsSplitScreenMultiWindow());
assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled",
getFakeFocusEnabledConfig());
try (var splitScreen = new SplitScreenActivitiesCloseable(
ENABLE_FAKE_FOCUS_OPT_IN_LEFT_ACTIVITY,
ENABLE_FAKE_FOCUS_OPT_IN_RIGHT_ACTIVITY)) {
waitAssertEquals("expected should send compat fake focus", /* expected */ true,
() -> splitScreen.getPrimaryActivity()
.getActivityState().getShouldSendCompatFakeFocus());
}
}
@Test
@EnableCompatChanges({OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS})
public void testEnableFakeFocus_overrideApplied_returnsTrue() {
assumeTrue("Skipping test: no split multi-window support",
supportsSplitScreenMultiWindow());
assumeTrue("Skipping test: config_isCompatFakeFocusEnabled not enabled",
getFakeFocusEnabledConfig());
try (var splitScreen = new SplitScreenActivitiesCloseable(
RESIZEABLE_LEFT_ACTIVITY,
RESIZEABLE_RIGHT_ACTIVITY)) {
waitAssertEquals("expected should send compat fake focus", /* expected */ true,
() -> splitScreen.getPrimaryActivity()
.getActivityState().getShouldSendCompatFakeFocus());
}
}
boolean getFakeFocusEnabledConfig() {
return mContext.getResources().getBoolean(
Resources.getSystem().getIdentifier(
"config_isCompatFakeFocusEnabled",
"bool", "android"));
}
@Test
@Ignore("b/295873734 flaky")
public void testOverrideIgnoreRequestedOrientation_propertyIsFalse_overrideNotApplied() {
assumeTrue("Skipping test: "
+ "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled",
isPolicyForIgnoringRequestedOrientationEnabled());
try (var compatChange = new CompatChangeCloseable(
ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION,
OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY.getPackageName());
var session = new ActivitySessionCloseable(
OPT_OUT_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) {
waitAssertEquals("expected landscape orientation",
SCREEN_ORIENTATION_LANDSCAPE,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
public void testOverrideIgnoreRequestedOrientation_isDisabled_propertyIsTrue_overrideApplied() {
assumeTrue("Skipping test: "
+ "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled",
isPolicyForIgnoringRequestedOrientationEnabled());
try (var session = new ActivitySessionCloseable(
OPT_IN_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) {
waitAssertEquals("expected portrait orientation",
SCREEN_ORIENTATION_PORTRAIT,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION})
public void testOverrideIgnoreRequestedOrientation() {
assumeTrue("Skipping test: "
+ "config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled not enabled",
isPolicyForIgnoringRequestedOrientationEnabled());
try (var session = new ActivitySessionCloseable(
NO_PROPERTY_CHANGE_ORIENTATION_WHILE_RELAUNCHING_ACTIVITY)) {
waitAssertEquals("expected portrait orientation",
SCREEN_ORIENTATION_PORTRAIT,
() -> session.getActivityState().getOverrideOrientation());
}
}
@Test
public void testOptOutPropertyCameraCompatForceRotation_rotationDisabled() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
// Activity without property or override is eligible for force rotation.
waitAssertEquals("expected to force rotate for camera compat",
/* expected */ true,
() -> session.getActivityState().getShouldForceRotateForCameraCompat());
}
try (var session = new ActivitySessionCloseable(
CAMERA_COMPAT_ALLOW_FORCE_ROTATION_ACTIVITY)) {
waitAssertEquals("expected to not force rotate for camera compat",
/* expected */ false,
() -> session.getActivityState().getShouldForceRotateForCameraCompat());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION})
public void testOverrideForCameraCompatForceRotation_rotationDisabled() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
waitAssertEquals("expected to not force rotate for camera compat",
/* expected */ false,
() -> session.getActivityState().getShouldForceRotateForCameraCompat());
}
}
@Test
public void testOptOutPropertyCameraCompatRefresh() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
// Activity without property or override is eligible for refresh.
waitAssertEquals("expected to refresh activity for camera compat",
/* expected */ true,
() -> session.getActivityState().getShouldRefreshActivityForCameraCompat());
}
try (var session = new ActivitySessionCloseable(CAMERA_COMPAT_ALLOW_REFRESH_ACTIVITY)) {
waitAssertEquals("expected to not refresh activity for camera compat",
/* expected */ false,
() -> session.getActivityState().getShouldRefreshActivityForCameraCompat());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH})
public void testOverrideForCameraCompatRefresh() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
waitAssertEquals("expected to not refresh activity for camera compat",
/* expected */ false,
() -> session.getActivityState().getShouldRefreshActivityForCameraCompat());
}
}
@Test
public void testOptInPropertyCameraCompatRefreshViaPause() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
// Activity without property or override doesn't refresh via
// "resumed -> paused -> resumed".
waitAssertEquals("expected to not refresh activity via pause for camera compat",
/* expected */ false,
() -> session.getActivityState()
.getShouldRefreshActivityViaPauseForCameraCompat());
}
try (var session = new ActivitySessionCloseable(
CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_IN_ACTIVITY)) {
waitAssertEquals("expected to Refresh activity via pause for camera compat",
/* expected */ true,
() -> session.getActivityState()
.getShouldRefreshActivityViaPauseForCameraCompat());
}
}
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE})
public void testOverrideForCameraCompatRefreshViaPause() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var session = new ActivitySessionCloseable(RESIZEABLE_PORTRAIT_ACTIVITY)) {
waitAssertEquals("expected to Refresh activity via pause for camera compat",
/* expected */ true,
() -> session.getActivityState()
.getShouldRefreshActivityViaPauseForCameraCompat());
}
}
@Test
public void testOptOutPropertyCameraCompatRefreshViaPause() {
assumeTrue("Skipping test: config_isWindowManagerCameraCompatTreatmentEnabled not enabled",
isCameraCompatForceRotationTreatmentConfigEnabled());
try (var compatChange = new CompatChangeCloseable(
ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY.getPackageName());
var session = new ActivitySessionCloseable(
CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE_OPT_OUT_ACTIVITY)) {
waitAssertEquals("expected to not refresh activity via pause for camera compat",
/* expected */ false,
() -> session.getActivityState()
.getShouldRefreshActivityViaPauseForCameraCompat());
}
}
/**
* Test that a non-resizeable portrait activity enters size compat mode after resizing the
* display.
*/
@Test
public void testSizeCompatForNonResizeableActivity() {
runSizeCompatTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true);
}
/**
* Test that a non-resizeable portrait activity doesn't enter size compat mode after resizing
* the display, when the {@link ActivityInfo#FORCE_RESIZE_APP} compat change is enabled.
*/
@Test
@EnableCompatChanges({ActivityInfo.FORCE_RESIZE_APP})
public void testSizeCompatForNonResizeableActivityForceResizeEnabled() {
runSizeCompatTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /*inSizeCompatModeAfterResize */ false);
}
/**
* Test that a resizeable portrait activity doesn't enter size compat mode after resizing
* the display.
*/
@Test
public void testSizeCompatForResizeableActivity() {
runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ false);
}
/**
* Test that a non-resizeable portrait activity that supports size changes doesn't enter size
* compat mode after resizing the display.
*/
@Test
public void testSizeCompatForSupportsSizeChangesActivity() {
runSizeCompatTest(
SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ false);
}
/**
* Test that a resizeable portrait activity enters size compat mode after resizing
* the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat change is enabled.
*/
@Test
@EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
public void testSizeCompatForResizeableActivityForceNonResizeEnabled() {
runSizeCompatTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true);
}
/**
* Test that a non-resizeable portrait activity that supports size changes enters size compat
* mode after resizing the display, when the {@link ActivityInfo#FORCE_NON_RESIZE_APP} compat
* change is enabled.
*/
@Test
@EnableCompatChanges({ActivityInfo.FORCE_NON_RESIZE_APP})
public void testSizeCompatForSupportsSizeChangesActivityForceNonResizeEnabled() {
runSizeCompatTest(
SUPPORTS_SIZE_CHANGES_PORTRAIT_ACTIVITY, /* inSizeCompatModeAfterResize */ true);
}
/**
* Test that a min aspect ratio activity eligible for size compat mode results in sandboxed
* Display APIs.
*/
@Test
public void testSandboxForNonResizableAspectRatioActivity() {
runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
/* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true);
}
// =================
// NEVER_SANDBOX test cases
// =================
// Validates that an activity forced into size compat mode never has sandboxing applied to the
// max bounds. It is expected that an activity in size compat mode normally always has
// sandboxing applied.
/**
* Test that a min aspect ratio activity eligible for size compat mode does not have the Display
* APIs sandboxed when the {@link ActivityInfo#NEVER_SANDBOX_DISPLAY_APIS} compat change is
* enabled.
*/
@Test
@EnableCompatChanges({ActivityInfo.NEVER_SANDBOX_DISPLAY_APIS})
public void testSandboxForNonResizableAspectRatioActivityNeverSandboxDisplayApisEnabled() {
runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
/* isSandboxed */ false, /* inSizeCompatModeAfterResize */ true);
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does not have the
* Display APIs sandboxed when the 'never_constrain_display_apis_all_packages' Device Config
* flag is true.
*/
@Test
public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigAllPackagesFlagTrue() {
try (var neverForAll = setNeverConstrainDisplayApisAllPackagesFlag("true");
var neverAnApp = setNeverConstrainDisplayApisFlag("com.android.other::")) {
runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
/* isSandboxed */ false, /* inSizeCompatModeAfterResize */ true);
}
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does not have the Display
* APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
* package with an open ended range.
*/
@Test
public void testSandboxForNonResizableActivityPackageUnboundedInNeverSandboxDeviceConfigFlag() {
ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
try (var neverForApp = setNeverConstrainDisplayApisFlag(
"com.android.other::," + activity.getPackageName() + "::")) {
runSizeCompatModeSandboxTest(activity, /* isSandboxed */ false,
/* inSizeCompatModeAfterResize */ true);
}
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does not have the Display
* APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
* package with a version range that matches the installed version of the package.
*/
@Test
public void testSandboxForNonResizableActivityPackageWithinRangeInNeverSandboxDeviceConfig() {
ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
long version = getPackageVersion(activity);
try (var neverForApp = setNeverConstrainDisplayApisFlag(
"com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
version - 1) + ":" + String.valueOf(version + 1))) {
runSizeCompatModeSandboxTest(activity, /* isSandboxed */ false,
/* inSizeCompatModeAfterResize */ true);
}
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does have the Display
* APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains the test
* package with a version range that doesn't match the installed version of the package.
*/
@Test
public void testSandboxForNonResizableActivityPackageOutsideRangeInNeverSandboxDeviceConfig() {
ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
long version = getPackageVersion(activity);
try (var neverForApp = setNeverConstrainDisplayApisFlag(
"com.android.other::," + activity.getPackageName() + ":" + String.valueOf(
version + 1) + ":")) {
runSizeCompatModeSandboxTest(activity, /* isSandboxed */ true,
/* inSizeCompatModeAfterResize */ true);
}
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does have the Display
* APIs sandboxed when the 'never_constrain_display_apis' Device Config flag doesn't contain the
* test package.
*/
@Test
public void testSandboxForNonResizableActivityPackageNotInNeverSandboxDeviceConfigFlag() {
try (var neverForApp = setNeverConstrainDisplayApisFlag(
"com.android.other::,com.android.other2::")) {
runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
/* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true);
}
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does have the Display
* APIs sandboxed when the 'never_constrain_display_apis' Device Config flag is empty.
*/
@Test
public void testSandboxForNonResizableActivityNeverSandboxDeviceConfigFlagEmpty() {
try (var empty = setNeverConstrainDisplayApisFlag("")) {
runSizeCompatModeSandboxTest(NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY,
/* isSandboxed */ true, /* inSizeCompatModeAfterResize */ true);
}
}
/**
* Test that a min aspect ratio activity eligible for size compat mode does have the Display
* APIs sandboxed when the 'never_constrain_display_apis' Device Config flag contains an invalid
* entry for the test package.
*/
@Test
public void testSandboxForNonResizableActivityInvalidEntryInNeverSandboxDeviceConfigFlag() {
ComponentName activity = NON_RESIZEABLE_LARGE_ASPECT_RATIO_ACTIVITY;
try (var neverForApp = setNeverConstrainDisplayApisFlag(
"com.android.other::," + activity.getPackageName() + ":::")) {
runSizeCompatModeSandboxTest(activity, /* isSandboxed */ true,
/* inSizeCompatModeAfterResize */ true);
}
}
/** =================
* SANDBOX_VIEW_BOUNDS_APIS test cases
* @see #testSandbox_viewApiForLetterboxedActivity
* @see #testNoSandbox_viewApiForLetterboxedActivity
* @see #testNoSandbox_viewApiForLetterboxedActivityOptOut
* =================
* Validates that an activity in letterbox mode has sandboxing applied to the
* view bounds when OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS is set.
* Without this flag or with
* {@link android.view.WindowManager#PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS}
* value=false in AndroidManifest.xml
* {@link android.view.View#getLocationOnScreen},
* {@link android.view.View#getWindowDisplayFrame}
* {@link android.view.View#getBoundsOnScreen}
* and {@link android.view.View#getWindowVisibleDisplayFrame}
* return location or display frame offset by the window location on the screen:
* {@link WindowConfiguration#getBounds}
*/
@Test
public void testSandbox_viewApiForLetterboxedActivity() {
// Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application
try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f);
var compatChange = new CompatChangeCloseable(
OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS,
TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY.getPackageName());
var receiver = new BroadcastReceiverCloseable(mContext,
ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED)) {
try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY)) {
// Wait for the broadcast action
boolean testPassed = receiver
.getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_ALLOWED_PASSED)
.block(TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS);
assertThat(testPassed).isTrue();
}
}
}
@Test
public void testNoSandbox_viewApiForLetterboxedActivity() {
// Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application
try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f);
var receiver = new BroadcastReceiverCloseable(mContext,
ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED)) {
try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_ALLOWED_ACTIVITY)) {
// Wait for the broadcast action
boolean testPassed = receiver
.getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_NOT_ALLOWED_PASSED)
.block(TEST_VIEW_SANDBOX_ALLOWED_TIMEOUT_MS);
assertThat(testPassed).isTrue();
}
}
}
@Test
public void testNoSandbox_viewApiForLetterboxedActivityOptOut() {
// Enable OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS changeId for the test application
try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f);
var compatChange = new CompatChangeCloseable(
OVERRIDE_SANDBOX_VIEW_BOUNDS_APIS,
TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY.getPackageName());
var receiver = new BroadcastReceiverCloseable(mContext,
ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED)) {
try (var session = new ActivitySessionCloseable(TEST_VIEW_SANDBOX_OPT_OUT_ACTIVITY)) {
// Wait for the broadcast action
boolean testPassed = receiver
.getBroadcastReceivedVariable(ACTION_TEST_VIEW_SANDBOX_OPT_OUT_PASSED)
.block(TEST_VIEW_SANDBOX_OPT_OUT_TIMEOUT_MS);
assertThat(testPassed).isTrue();
}
}
}
// =================
// ALWAYS_SANDBOX test cases
// =================
// Validates that an activity simply in letterbox mode has sandboxing applied to the max
// bounds when ALWAYS_SANDBOX is set. Without the flag, we would not expect a letterbox activity
// to be sandboxed, unless it is also eligible for size compat mode.
/**
* Test that a portrait activity not eligible for size compat mode does have the
* Display APIs sandboxed when the {@link ActivityInfo#ALWAYS_SANDBOX_DISPLAY_APIS} compat
* change is enabled.
*/
@Test
@EnableCompatChanges({ActivityInfo.ALWAYS_SANDBOX_DISPLAY_APIS})
public void testSandboxForResizableActivityAlwaysSandboxDisplayApisEnabled() {
runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed */ true);
}
/**
* Test that a portrait activity not eligible for size compat mode does not have the
* Display APIs sandboxed when the 'always_constrain_display_apis' Device Config flag is empty.
*/
@Test
public void testSandboxResizableActivityAlwaysSandboxDeviceConfigFlagEmpty() {
runLetterboxSandboxTest(RESIZEABLE_PORTRAIT_ACTIVITY, /* isSandboxed */ false);
}
/**
* Test that a portrait activity not eligible for size compat mode does have the Display
* APIs sandboxed when the 'always_constrain_display_apis' Device Config flag contains the test
* package.
*/
@Test
public void testSandboxResizableActivityPackageInAlwaysSandboxDeviceConfigFlag() {
ComponentName activity = RESIZEABLE_PORTRAIT_ACTIVITY;
try (var alwaysForApp = setAlwaysConstrainDisplayApisFlag(
"com.android.other::," + activity.getPackageName() + "::")) {
runLetterboxSandboxTest(activity, /* isSandboxed */ true);
}
}
/**
* Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO} has no effect on its
* own. The aspect ratio of the activity should be the same as that of the task, which should be
* in line with that of the display.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
public void testOverrideMinAspectRatioMissingSpecificOverride() {
runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected */ 0);
}
/**
* Test that only applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
* its own without the presence of {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO}.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioMissingGeneralOverride() {
runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY, /* expected */ 0);
}
/**
* Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
* activities whose orientation is fixed to landscape.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioForLandscapeActivity() {
runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected */ 0);
}
/**
* Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
* activities whose orientation isn't fixed.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
@DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
public void testOverrideMinAspectRatioForNonFixedOrientationActivityPortraitOnlyDisabled() {
runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected */
OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
}
/**
* Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
* activities whose orientation is fixed to landscape.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
@DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY})
public void testOverrideMinAspectRatioForLandscapeActivityPortraitOnlyDisabled() {
runMinAspectRatioTest(NON_RESIZEABLE_LANDSCAPE_ACTIVITY, /* expected */
OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
}
/**
* Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} has no effect on
* activities whose orientation isn't fixed.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioForNonFixedOrientationActivity() {
runMinAspectRatioTest(NON_RESIZEABLE_NON_FIXED_ORIENTATION_ACTIVITY, /* expected */ 0);
}
/**
* Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE} sets the min aspect
* ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE}.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioLargeAspectRatio() {
runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
/* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
}
/**
* Test that applying {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM} sets the min aspect
* ratio to {@link ActivityInfo#OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE}.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioMediumAspectRatio() {
runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
/* expected */ OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
}
/**
* Test that applying multiple min aspect ratio overrides result in the largest one taking
* effect.
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioBothAspectRatios() {
runMinAspectRatioTest(NON_RESIZEABLE_PORTRAIT_ACTIVITY,
/* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
}
/**
* Test that the min aspect ratio of the activity as defined in the manifest is ignored if
* there is an override for a larger min aspect ratio present (16:9 > 1.6).
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
public void testOverrideMinAspectRatioActivityMinAspectRatioSmallerThanOverride() {
runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
/* expected */ OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
}
/**
* Test that the min aspect ratio of the activity as defined in the manifest is upheld if
* there is an override for a smaller min aspect ratio present (3:2 < 1.6).
*/
@Test
@EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
public void testOverrideMinAspectRatioActivityMinAspectRatioLargerThanOverride() {
runMinAspectRatioTest(NON_RESIZEABLE_ASPECT_RATIO_ACTIVITY,
/* expected */ ACTIVITY_MIN_ASPECT_RATIO);
}
private boolean isCameraCompatForceRotationTreatmentConfigEnabled() {
return getBooleanConfig("config_isWindowManagerCameraCompatTreatmentEnabled");
}
private boolean isPolicyForIgnoringRequestedOrientationEnabled() {
return getBooleanConfig("config_letterboxIsPolicyForIgnoringRequestedOrientationEnabled");
}
private boolean getBooleanConfig(String configName) {
return mContext.getResources().getBoolean(
Resources.getSystem().getIdentifier(configName, "bool", "android"));
}
/**
* Launches the provided activity into size compat mode twice. The first time, the display
* is resized to be half the size. The second time, the display is resized to be twice the
* original size.
*
* @param activity the activity under test.
* @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
* resizing the display
*/
private void runSizeCompatTest(ComponentName activity, boolean inSizeCompatModeAfterResize) {
try (var session = new ActivitySessionCloseable(activity)) {
runSizeCompatTestForActivity(activity, /* resizeRatio */ 0.5,
inSizeCompatModeAfterResize);
}
try (var session = new ActivitySessionCloseable(activity)) {
runSizeCompatTestForActivity(activity, /* resizeRatio */ 2,
inSizeCompatModeAfterResize);
}
}
/**
* Launches the provided activity on the default display, initially not in size compat mode.
* After resizing the display, verifies if activity is in size compat mode or not
*
* @param activity the activity under test
* @param resizeRatio the ratio to resize the display
* @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
* resizing the display
*/
private void runSizeCompatTestForActivity(ComponentName activity, final double resizeRatio,
final boolean inSizeCompatModeAfterResize) {
waitAndAssertSizeCompatMode(activity, /* expectedInSizeCompatMode */ false);
try (var resized = new DisplaySizeScaleCloseable(resizeRatio, activity)) {
assertThat(resized.getInitialDisplayAspectRatio())
.isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
waitAndAssertSizeCompatMode(activity, inSizeCompatModeAfterResize);
}
}
private void waitAndAssertSizeCompatMode(final ComponentName activity,
final boolean expectedInSizeCompatMode) {
waitAssertEquals("The window must be inSizeCompatMode==" + expectedInSizeCompatMode,
expectedInSizeCompatMode,
() -> getActivityWaitState(activity).inSizeCompatMode());
}
/**
* Similar to {@link #runSizeCompatTest(ComponentName, boolean)}, but the activity is
* expected to be in size compat mode after resizing the display.
*
* @param activity the activity under test
* @param isSandboxed when {@code true},
* {@link android.app.WindowConfiguration#getMaxBounds()}
* are sandboxed to the activity bounds. Otherwise, they
* inherit the
* DisplayArea bounds
* @param inSizeCompatModeAfterResize if the activity should be in size compat mode after
* resizing the display
*/
private void runSizeCompatModeSandboxTest(ComponentName activity, boolean isSandboxed,
boolean inSizeCompatModeAfterResize) {
try (var session = new ActivitySessionCloseable(activity, WINDOWING_MODE_FULLSCREEN)) {
runSizeCompatTestForActivity(activity, /* resizeRatio */ 0.5,
inSizeCompatModeAfterResize);
assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
assertSandboxedByBounds(activity, isSandboxed);
}
try (var session = new ActivitySessionCloseable(activity, WINDOWING_MODE_FULLSCREEN)) {
runSizeCompatTestForActivity(activity, /* resizeRatio */ 2,
inSizeCompatModeAfterResize);
assertSandboxedByProvidesMaxBounds(activity, isSandboxed);
assertSandboxedByBounds(activity, isSandboxed);
}
}
/**
* Similar to {@link #runSizeCompatModeSandboxTest(ComponentName, boolean, boolean)}, but the
* activity is put into letterbox mode after resizing the display.
*
* @param activityName the activity under test
* @param isSandboxed when {@code true}, {@link android.app.WindowConfiguration#getMaxBounds()}
* are sandboxed to the activity bounds. Otherwise, they inherit the
* DisplayArea bounds
*/
private void runLetterboxSandboxTest(ComponentName activityName, boolean isSandboxed) {
try (var aspectRatio = new DisplayAspectRatioCloseable(ORIENTATION_LANDSCAPE, 2.0f)) {
assertThat(aspectRatio.getInitialDisplayAspectRatio())
.isLessThan(ACTIVITY_LARGE_MIN_ASPECT_RATIO);
try (var session = new ActivitySessionCloseable(activityName)) {
assertSandboxedByProvidesMaxBounds(activityName, isSandboxed);
}
}
}
private void assertSandboxedByBounds(ComponentName activityName, boolean isSandboxed) {
final WindowManagerState.Activity activity = getActivityWaitState(activityName);
assertNotNull(activity);
final Rect activityBounds = activity.getBounds();
final Rect maxBounds = activity.getMaxBounds();
WindowManagerState.DisplayArea tda = mWmState.getTaskDisplayArea(activityName);
assertNotNull(tda);
if (isSandboxed) {
assertEquals(
"The window has max bounds sandboxed to the window bounds",
activityBounds, maxBounds);
} else {
assertEquals(
"The window is not sandboxed, with max bounds reflecting the DisplayArea",
tda.getBounds(), maxBounds);
}
}
private void assertSandboxedByProvidesMaxBounds(ComponentName activityName,
boolean isSandboxed) {
final WindowManagerState.Activity activity = getActivityWaitState(activityName);
assertNotNull(activity);
if (isSandboxed) {
assertTrue(
"The window should have max bounds sandboxed to the window bounds",
activity.providesMaxBounds());
} else {
assertFalse(
"The window should not be sandboxed; max bounds should reflect the DisplayArea",
activity.providesMaxBounds());
}
}
private CloseableDeviceConfig setNeverConstrainDisplayApisFlag(@Nullable String value) {
return setConstrainDisplayApisFlag("never_constrain_display_apis", value);
}
private CloseableDeviceConfig setNeverConstrainDisplayApisAllPackagesFlag(
@Nullable String value) {
return setConstrainDisplayApisFlag("never_constrain_display_apis_all_packages", value);
}
private CloseableDeviceConfig setAlwaysConstrainDisplayApisFlag(
@Nullable String value) {
return setConstrainDisplayApisFlag("always_constrain_display_apis", value);
}
private CloseableDeviceConfig setConstrainDisplayApisFlag(@NonNull String flagName,
@Nullable String value) {
return new CloseableDeviceConfig(NAMESPACE_CONSTRAIN_DISPLAY_APIS, flagName, value);
}
/**
* Launches the provided activity and verifies that its min aspect ratio is equal to {@code
* expected}.
*
* @param activity the activity under test.
* @param expected the expected min aspect ratio in both portrait and landscape displays.
*/
private void runMinAspectRatioTest(ComponentName activity, float expected) {
try (var session = new ActivitySessionCloseable(activity)) {
assertNotNull(session.getActivityState());
assertEquals(expected,
session.getActivityState().getMinAspectRatio(),
FLOAT_EQUALITY_DELTA);
}
}
private long getPackageVersion(ComponentName activity) {
try {
return mContext.getPackageManager().getPackageInfo(activity.getPackageName(),
/* flags */ 0).getLongVersionCode();
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}
private static ComponentName component(Class<? extends Activity> activity) {
return new ComponentName(getInstrumentation().getContext(), activity);
}
public static class ResizeablePortraitActivity extends AbstractLifecycleLogActivity {
}
public static class ResponsiveActivity extends AbstractLifecycleLogActivity {
}
public static class NonResizeablePortraitActivity extends AbstractLifecycleLogActivity {
}
public static class NonResizeableLandscapeActivity extends AbstractLifecycleLogActivity {
}
public static class NonResizeableNonFixedOrientationActivity extends
AbstractLifecycleLogActivity {
}
public static class NonResizeableAspectRatioActivity extends AbstractLifecycleLogActivity {
}
public static class NonResizeableLargeAspectRatioActivity extends AbstractLifecycleLogActivity {
}
public static class SupportsSizeChangesPortraitActivity extends AbstractLifecycleLogActivity {
}
public static class ResizeableLeftActivity extends AbstractLifecycleLogActivity {
}
public static class ResizeableRightActivity extends AbstractLifecycleLogActivity {
}
public static class NoPropertyChangeOrientationWhileRelaunchingActivity extends
AbstractLifecycleLogActivity {
private static boolean sHasChangeOrientationInOnResume;
@Override
protected void onCreate(Bundle instance) {
super.onCreate(instance);
// When OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION is enabled this request
// should be ignored if sHasChangeOrientationInOnResume is true.
setRequestedOrientation(SCREEN_ORIENTATION_LANDSCAPE);
}
@Override
protected void onResume() {
super.onResume();
if (!sHasChangeOrientationInOnResume) {
setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT);
sHasChangeOrientationInOnResume = true;
}
}
}
/**
* Registers broadcast receiver which receives result actions from Activities under test.
*/
private static class BroadcastReceiverCloseable implements AutoCloseable {
private final Context mContext;
private final Map<String, ConditionVariable> mBroadcastsReceived;
private final BroadcastReceiver mAppCommunicator = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
getBroadcastReceivedVariable(intent.getAction()).open();
}
};
BroadcastReceiverCloseable(final Context context, final String action) {
this.mContext = context;
// Keep the received broadcast items in the map.
mBroadcastsReceived = Collections.synchronizedMap(new HashMap<>());
// Register for broadcast actions.
IntentFilter filter = new IntentFilter();
filter.addAction(action);
mContext.registerReceiver(mAppCommunicator, filter, Context.RECEIVER_EXPORTED);
}
ConditionVariable getBroadcastReceivedVariable(String action) {
return mBroadcastsReceived.computeIfAbsent(action, key -> new ConditionVariable());
}
@Override
public void close() {
mContext.unregisterReceiver(mAppCommunicator);
}
}
/**
* Resets device config to the original value after the try-with-resources block finishes
* try (var dc = new CloseableDeviceConfig()) {...}
*/
private class CloseableDeviceConfig implements AutoCloseable {
private String mOriginalValue;
private final String mNamespace;
private final String mFlagName;
CloseableDeviceConfig(@NonNull String namespace, @NonNull String flagName,
@Nullable String value) {
mNamespace = namespace;
mFlagName = flagName;
runWithShellPermission(() -> {
mOriginalValue = DeviceConfig.getProperty(namespace, flagName);
DeviceConfig.setProperty(namespace, flagName, value, /* makeDefault */
false);
});
}
@Override
public void close() {
runWithShellPermission(() -> DeviceConfig.setProperty(mNamespace, mFlagName,
mOriginalValue, /* makeDefault */ false));
}
}
}