diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index c384fbce75501..80cea55111ba4 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1237,6 +1237,29 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @Overridable public static final long OVERRIDE_ANY_ORIENTATION = 265464455L; + /** + * This override fixes display orientation to landscape natural orientation when a task is + * fullscreen. While display rotation is fixed to landscape, the orientation requested by the + * activity will be still respected by bounds resolution logic. For instance, if an activity + * requests portrait orientation and this override is set, then activity will appear in the + * letterbox mode for fixed orientation with the display rotated to the lanscape natural + * orientation. + * + *
This override is applicable only when natural orientation of the device is + * landscape and display ignores orientation requestes. + * + *
Main use case for this override are camera-using activities that are portrait-only and + * assume alignment with natural device orientation. Such activities can automatically be + * rotated with com.android.server.wm.DisplayRotationCompatPolicy but not all of them can + * handle dynamic rotation and thus can benefit from this override. + * + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; + /** * Compares activity window layout min width/height with require space for multi window to * determine if it can be put into multi window mode. diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 1e1e2b9c85d61..e3bf2d4f436f5 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -1015,6 +1015,51 @@ public interface WindowManager extends ViewManager { String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"; + /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity should be opted-out from the + * compatibility override that fixes display orientation to landscape natural orientation when + * an activity is fullscreen. + * + *
When this compat override is enabled and while display is fixed to the landscape natural + * orientation, the orientation requested by the activity will be still respected by bounds + * resolution logic. For instance, if an activity requests portrait orientation, then activity + * will appear in the letterbox mode for fixed orientation with the display rotated to the + * lanscape natural orientation. + * + *
The treatment is disabled by default but device manufacturers can enable the treatment + * using their discretion to improve display compatibility on the displays that have + * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed + * orientation, see Enhanced letterboxing + * for more details). + * + *
With this property set to {@code true} or unset, the system wiil use landscape display + * orientation when the following conditions are met: + *
With this property set to {@code false}, device manufactured per-app override for + * display orientation won't be applied. + * + *
Syntax: + *
+ * <activity> + * <property + * android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE" + * android:value="true|false"/> + * </activity> + *+ * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = + "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"; + /** * @hide */ diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index f47d9c6e0c2d6..1cf819af7a243 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3331,6 +3331,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "1015746067": { + "message": "Display id=%d is ignoring orientation request for %d, return %d following a per-app override for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "1022095595": { "message": "TaskFragment info changed name=%s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index d5b90aa861456..5767730abd82b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -27,6 +27,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -2731,6 +2732,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int orientation = super.getOrientation(); if (!handlesOrientationChangeFromDescendant(orientation)) { + ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true); + if (topActivity != null && topActivity.mLetterboxUiController + .shouldUseDisplayLandscapeNaturalOrientation()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Display id=%d is ignoring orientation request for %d, return %d" + + " following a per-app override for %s", + mDisplayId, orientation, SCREEN_ORIENTATION_LANDSCAPE, topActivity); + return SCREEN_ORIENTATION_LANDSCAPE; + } mLastOrientationSource = null; // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation ProtoLog.v(WM_DEBUG_ORIENTATION, diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index d73be18e15e9c..c5a50cad41d89 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -25,6 +25,7 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQU import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; +import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; 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; @@ -38,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; @@ -72,6 +74,9 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_RE import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString; +import static java.lang.Boolean.FALSE; +import static java.lang.Boolean.TRUE; + import android.annotation.Nullable; import android.app.ActivityManager.TaskDescription; import android.content.pm.ActivityInfo.ScreenOrientation; @@ -131,9 +136,23 @@ final class LetterboxUiController { private final boolean mIsOverrideToNosensorOrientationEnabled; // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE private final boolean mIsOverrideToReverseLandscapeOrientationEnabled; + // Corresponds to OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION + private final boolean mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled; + + // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION + private final boolean mIsOverrideCameraCompatDisableForceRotationEnabled; + // Corresponds to OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH + private final boolean mIsOverrideCameraCompatDisableRefreshEnabled; + // Corresponds to OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE + private final boolean mIsOverrideCameraCompatEnableRefreshViaPauseEnabled; + + // Corresponds to OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION + private final boolean mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled; @Nullable private final Boolean mBooleanPropertyAllowOrientationOverride; + @Nullable + private final Boolean mBooleanPropertyAllowDisplayOrientationOverride; /* * WindowContainerListener responsible to make translucent activities inherit @@ -222,6 +241,10 @@ final class LetterboxUiController { readComponentProperty(packageManager, mActivityRecord.packageName, /* gatingCondition */ null, PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + mBooleanPropertyAllowDisplayOrientationOverride = + readComponentProperty(packageManager, mActivityRecord.packageName, + /* gatingCondition */ null, + PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE); mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION); mIsOverrideToPortraitOrientationEnabled = @@ -230,6 +253,18 @@ final class LetterboxUiController { isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE); mIsOverrideToNosensorOrientationEnabled = isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); + mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION); + + mIsOverrideCameraCompatDisableForceRotationEnabled = + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION); + mIsOverrideCameraCompatDisableRefreshEnabled = + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH); + mIsOverrideCameraCompatEnableRefreshViaPauseEnabled = + isCompatChangeEnabled(OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); + + mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION); } /** @@ -299,7 +334,7 @@ final class LetterboxUiController { if (!shouldEnableWithOverrideAndProperty( /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, - OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, + mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled, mBooleanPropertyIgnoreRequestedOrientation)) { return false; } @@ -344,9 +379,34 @@ final class LetterboxUiController { mIsRefreshAfterRotationRequested = isRequested; } + /** + * Whether should fix display orientation to landscape natural orientation when a task is + * fullscreen and the display is ignoring orientation requests. + * + *
This treatment is enabled when the following conditions are met: + *
This is used for the treatments that are enabled based with the heuristic but can be + * disabled on per-app basis by OEMs or app developers. + */ + private boolean shouldEnableWithOptInOverrideAndOptOutProperty(BooleanSupplier gatingCondition, + boolean isOverrideChangeEnabled, Boolean property) { + if (!gatingCondition.getAsBoolean()) { + return false; + } + return !FALSE.equals(property) && isOverrideChangeEnabled; } /** @@ -468,20 +547,20 @@ final class LetterboxUiController { *
This is used for the treatments that are enabled only on per-app basis. */ private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, - long overrideChangeId, Boolean property) { + boolean isOverrideChangeEnabled, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } - if (Boolean.FALSE.equals(property)) { + if (FALSE.equals(property)) { return false; } - return Boolean.TRUE.equals(property) || isCompatChangeEnabled(overrideChangeId); + return TRUE.equals(property) || isOverrideChangeEnabled; } boolean hasWallpaperBackgroundForLetterbox() { diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index bc5e2dc65bc21..c7f19fb1099d5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -24,14 +24,18 @@ import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQU import static android.content.pm.ActivityInfo.OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE; import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR; import static android.content.pm.ActivityInfo.OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT; +import static android.content.pm.ActivityInfo.OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION; 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.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH; import static android.view.WindowManager.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; +import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; @@ -98,6 +102,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { public TestRule compatChangeRule = new PlatformCompatChangeRule(); private ActivityRecord mActivity; + private Task mTask; private DisplayContent mDisplayContent; private LetterboxUiController mController; private LetterboxConfiguration mLetterboxConfiguration; @@ -571,6 +576,56 @@ public class LetterboxUiControllerTest extends WindowTestsBase { /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED); } + // shouldUseDisplayLandscapeNaturalOrientation + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_override_returnsTrue() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + assertTrue(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_overrideAndFalseProperty_returnsFalse() + throws Exception { + mockThatProperty(PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_portraitNaturalOrientation_returnsFalse() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + doReturn(ORIENTATION_PORTRAIT).when(mDisplayContent).getNaturalOrientation(); + + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_disabledIgnoreOrientationRequest_returnsFalse() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + mDisplayContent.setIgnoreOrientationRequest(false); + + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + + @Test + @EnableCompatChanges({OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION}) + public void testShouldUseDisplayLandscapeNaturalOrientation_inMultiWindowMode_returnsFalse() { + prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation(); + + spyOn(mTask); + doReturn(true).when(mTask).inMultiWindowMode(); + + assertFalse(mController.shouldUseDisplayLandscapeNaturalOrientation()); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); @@ -579,6 +634,12 @@ public class LetterboxUiControllerTest extends WindowTestsBase { doReturn(property).when(pm).getProperty(eq(propertyName), anyString()); } + private void prepareActivityThatShouldUseDisplayLandscapeNaturalOrientation() { + spyOn(mDisplayContent); + doReturn(ORIENTATION_LANDSCAPE).when(mDisplayContent).getNaturalOrientation(); + mDisplayContent.setIgnoreOrientationRequest(true); + } + private void prepareActivityThatShouldIgnoreRequestedOrientationDuringRelaunch() { doReturn(true).when(mLetterboxConfiguration) .isPolicyForIgnoringRequestedOrientationEnabled(); @@ -588,10 +649,10 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private ActivityRecord setUpActivityWithComponent() { mDisplayContent = new TestDisplayContent .Builder(mAtm, /* dw */ 1000, /* dh */ 2000).build(); - Task task = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build(); + mTask = new TaskBuilder(mSupervisor).setDisplay(mDisplayContent).build(); final ActivityRecord activity = new ActivityBuilder(mAtm) .setOnTop(true) - .setTask(task) + .setTask(mTask) // Set the component to be that of the test class in order to enable compat changes .setComponent(ComponentName.createRelative(mContext, com.android.server.wm.LetterboxUiControllerTest.class.getName()))