Loading core/java/android/content/pm/ActivityInfo.java +35 −0 Original line number Diff line number Diff line Loading @@ -1057,6 +1057,41 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @TestApi public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id /** * This change id excludes the packages it is applied to from the camera compat force rotation * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context. * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION = 263959004L; // buganizer id /** * This change id excludes the packages it is applied to from activity refresh after camera * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for * context. * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id /** * This change id makes the packages it is applied to do activity refresh after camera compat * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed -> * ... -> stopped -> ... -> resumed" cycle. See * com.android.server.wm.DisplayRotationCompatPolicy for context. * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // buganizer id /** * This change id is the gatekeeper for all treatments that force a given min aspect ratio. * Enabling this change will allow the following min aspect ratio treatments to be applied: Loading core/java/android/view/WindowManager.java +137 −0 Original line number Diff line number Diff line Loading @@ -852,6 +852,143 @@ public interface WindowManager extends ViewManager { String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; /** * Activity level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be excluded from the * camera compatibility force rotation treatment. * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural * orientation of the device and set opposite to natural orientation for a landscape app * window. Mismatch between them can lead to camera issues like sideways or stretched * viewfinder since this is one of the strongest assumptions that apps make when they implement * camera previews. Since app and natural display orientations aren't guaranteed to match, the * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to * camera and is removed once camera is closed. * * <p>The camera compatibility can be enabled by device manufacturers on the displays that have * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> * for more details). * * <p>With this property set to {@code true} or unset, the system may apply the force rotation * treatment to fixed orientation activities. Device manufacturers can exclude packages from the * treatment using their discretion to improve display compatibility. * * <p>With this property set to {@code false}, the system will not apply the force rotation * treatment. * * <p><b>Syntax:</b> * <pre> * <activity> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION" * android:value="true|false"/> * </activity> * </pre> * * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; /** * Activity level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be excluded * from the activity "refresh" after the camera compatibility force rotation treatment. * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural * orientation of the device and set opposite to natural orientation for a landscape app * window. Mismatch between them can lead to camera issues like sideways or stretched * viewfinder since this is one of the strongest assumptions that apps make when they implement * camera previews. Since app and natural display orientations aren't guaranteed to match, the * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to * camera and is removed once camera is closed. * * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed -> * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context). * This allows to clear cached values in apps (e.g. display or camera rotation) that influence * camera preview and can lead to sideways or stretching issues persisting even after force * rotation. * * <p>The camera compatibility can be enabled by device manufacturers on the displays that have * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> * for more details). * * <p>With this property set to {@code true} or unset, the system may "refresh" activity after * the force rotation treatment. Device manufacturers can exclude packages from the "refresh" * using their discretion to improve display compatibility. * * <p>With this property set to {@code false}, the system will not "refresh" activity after the * force rotation treatment. * * <p><b>Syntax:</b> * <pre> * <activity> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH" * android:value="true|false"/> * </activity> * </pre> * * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; /** * Activity level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be or shouldn't be * "refreshed" after the camera compatibility force rotation treatment using "paused -> * resumed" cycle rather than "stopped -> resumed". * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural * orientation of the device and set opposite to natural orientation for a landscape app * window. Mismatch between them can lead to camera issues like sideways or stretched * viewfinder since this is one of the strongest assumptions that apps make when they implement * camera previews. Since app and natural display orientations aren't guaranteed to match, the * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to * camera and is removed once camera is closed. * * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed -> * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle * (if overridden by device manufacturers or using this property). This allows to clear cached * values in apps (e.g., display or camera rotation) that influence camera preview and can lead * to sideways or stretching issues persisting even after force rotation. * * <p>The camera compatibility can be enabled by device manufacturers on the displays that have * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> * for more details). * * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed" * cycle using their discretion to improve display compatibility. * * <p>With this property set to {@code true}, the system will "refresh" activity after the * force rotation treatment using "resumed -> paused -> resumed" cycle. * * <p>With this property set to {@code false}, the system will not "refresh" activity after the * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device * manufacturer adds the corresponding override. * * <p><b>Syntax:</b> * <pre> * <activity> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE" * android:value="true|false"/> * </activity> * </pre> * * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; /** * @hide */ Loading services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +9 −4 Original line number Diff line number Diff line Loading @@ -203,8 +203,11 @@ final class DisplayRotationCompatPolicy { || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { return; } boolean cycleThroughStop = mWmService.mLetterboxConfiguration .isCameraCompatRefreshCycleThroughStopEnabled(); boolean cycleThroughStop = mWmService.mLetterboxConfiguration .isCameraCompatRefreshCycleThroughStopEnabled() && !activity.mLetterboxUiController .shouldRefreshActivityViaPauseForCameraCompat(); try { activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true); ProtoLog.v(WM_DEBUG_STATES, Loading Loading @@ -255,7 +258,8 @@ final class DisplayRotationCompatPolicy { Configuration lastReportedConfig) { return newConfig.windowConfiguration.getDisplayRotation() != lastReportedConfig.windowConfiguration.getDisplayRotation() && isTreatmentEnabledForActivity(activity); && isTreatmentEnabledForActivity(activity) && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat(); } /** Loading Loading @@ -294,7 +298,8 @@ final class DisplayRotationCompatPolicy { // handle dynamic changes so we shouldn't force rotate them. && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED && mCameraIdPackageBiMap.containsPackageName(activity.packageName); && mCameraIdPackageBiMap.containsPackageName(activity.packageName) && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } private synchronized void notifyCameraOpened( Loading services/core/java/com/android/server/wm/LetterboxUiController.java +148 −9 Original line number Diff line number Diff line Loading @@ -17,12 +17,18 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 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_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; Loading Loading @@ -132,6 +138,15 @@ final class LetterboxUiController { @Nullable private Letterbox mLetterbox; @Nullable private final Boolean mBooleanPropertyCameraCompatAllowForceRotation; @Nullable private final Boolean mBooleanPropertyCameraCompatAllowRefresh; @Nullable private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause; // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. Loading @@ -154,8 +169,33 @@ final class LetterboxUiController { readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); mBooleanPropertyCameraCompatAllowForceRotation = readComponentProperty(packageManager, mActivityRecord.packageName, () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION); mBooleanPropertyCameraCompatAllowRefresh = readComponentProperty(packageManager, mActivityRecord.packageName, () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH); mBooleanPropertyCameraCompatEnableRefreshViaPause = readComponentProperty(packageManager, mActivityRecord.packageName, () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); } /** * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the * property isn't specified for the package. * * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the * property is unset. Particularly, when this returns {@code null}, {@link * #shouldEnableWithOverrideAndProperty} will check the value of override for the final * decision. */ @Nullable private static Boolean readComponentProperty(PackageManager packageManager, String packageName, BooleanSupplier gatingCondition, String propertyName) { Loading Loading @@ -210,15 +250,11 @@ final class LetterboxUiController { * </ul> */ boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) { return false; } if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) { return false; } if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation) && !mActivityRecord.info.isChangeEnabled( OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) { if (!shouldEnableWithOverrideAndProperty( /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, mBooleanPropertyIgnoreRequestedOrientation)) { return false; } if (mIsRelauchingAfterRequestedOrientationChanged) { Loading Loading @@ -262,6 +298,109 @@ final class LetterboxUiController { mIsRefreshAfterRotationRequested = isRequested; } /** * Whether activity is eligible for activity "refresh" after camera compat force rotation * treatment. See {@link DisplayRotationCompatPolicy} for context. * * <p>This treatment is enabled when the following conditions are met: * <ul> * <li>Flag gating the camera compat treatment is enabled. * <li>Activity isn't opted out by the device manufacturer with override or by the app * developers with the component property. * </ul> */ boolean shouldRefreshActivityForCameraCompat() { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH, mBooleanPropertyCameraCompatAllowRefresh); } /** * Whether activity should be "refreshed" after the camera compat force rotation treatment * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context. * * <p>This treatment is enabled when the following conditions are met: * <ul> * <li>Flag gating the camera compat treatment is enabled. * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the * component property by the app developers. * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device * manufacturer with override / by the app developers with the component property. * </ul> */ boolean shouldRefreshActivityViaPauseForCameraCompat() { return shouldEnableWithOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, mBooleanPropertyCameraCompatEnableRefreshViaPause); } /** * Whether activity is eligible for camera compat force rotation treatment. See {@link * DisplayRotationCompatPolicy} for context. * * <p>This treatment is enabled when the following conditions are met: * <ul> * <li>Flag gating the camera compat treatment is enabled. * <li>Activity isn't opted out by the device manufacturer with override or by the app * developers with the component property. * </ul> */ boolean shouldForceRotateForCameraCompat() { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION, mBooleanPropertyCameraCompatAllowForceRotation); } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} isn't {@code false} * <li>OEM didn't opt out with a {@code overrideChangeId} override * <li>App developers didn't opt out with a component {@code property} * </ul> * * <p>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 shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition, long overrideChangeId, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } return !Boolean.FALSE.equals(property) && !mActivityRecord.info.isChangeEnabled(overrideChangeId); } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} isn't {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>App developers opted in with a component {@code property} or an OEM opted in with a * component {@code property} * </ul> * * <p>This is used for the treatments that are enabled only on per-app basis. */ private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, long overrideChangeId, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } if (Boolean.FALSE.equals(property)) { return false; } return Boolean.TRUE.equals(property) || mActivityRecord.info.isChangeEnabled(overrideChangeId); } boolean hasWallpaperBackgroundForLetterbox() { return mShowWallpaperForLetterboxBackground; } Loading services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +40 −1 Original line number Diff line number Diff line Loading @@ -154,6 +154,18 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { assertNoForceRotationOrRefresh(); } @Test public void testTreatmentDisabledPerApp_noForceRotationOrRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); when(mActivity.mLetterboxUiController.shouldForceRotateForCameraCompat()) .thenReturn(false); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertNoForceRotationOrRefresh(); } @Test public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Loading Loading @@ -327,7 +339,21 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { } @Test public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception { public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()) .thenReturn(false); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); assertActivityRefreshRequested(/* refreshRequested */ false); } @Test public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh() throws Exception { when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); Loading Loading @@ -362,6 +388,19 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); } @Test public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat()) .thenReturn(true); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); } private void configureActivity(@ScreenOrientation int activityOrientation) { configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT); } Loading Loading
core/java/android/content/pm/ActivityInfo.java +35 −0 Original line number Diff line number Diff line Loading @@ -1057,6 +1057,41 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @TestApi public static final long ALWAYS_SANDBOX_DISPLAY_APIS = 185004937L; // buganizer id /** * This change id excludes the packages it is applied to from the camera compat force rotation * treatment. See com.android.server.wm.DisplayRotationCompatPolicy for context. * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION = 263959004L; // buganizer id /** * This change id excludes the packages it is applied to from activity refresh after camera * compat force rotation treatment. See com.android.server.wm.DisplayRotationCompatPolicy for * context. * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // buganizer id /** * This change id makes the packages it is applied to do activity refresh after camera compat * force rotation treatment using "resumed -> paused -> resumed" cycle rather than "resumed -> * ... -> stopped -> ... -> resumed" cycle. See * com.android.server.wm.DisplayRotationCompatPolicy for context. * @hide */ @ChangeId @Overridable @Disabled public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // buganizer id /** * This change id is the gatekeeper for all treatments that force a given min aspect ratio. * Enabling this change will allow the following min aspect ratio treatments to be applied: Loading
core/java/android/view/WindowManager.java +137 −0 Original line number Diff line number Diff line Loading @@ -852,6 +852,143 @@ public interface WindowManager extends ViewManager { String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION"; /** * Activity level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be excluded from the * camera compatibility force rotation treatment. * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural * orientation of the device and set opposite to natural orientation for a landscape app * window. Mismatch between them can lead to camera issues like sideways or stretched * viewfinder since this is one of the strongest assumptions that apps make when they implement * camera previews. Since app and natural display orientations aren't guaranteed to match, the * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to * camera and is removed once camera is closed. * * <p>The camera compatibility can be enabled by device manufacturers on the displays that have * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> * for more details). * * <p>With this property set to {@code true} or unset, the system may apply the force rotation * treatment to fixed orientation activities. Device manufacturers can exclude packages from the * treatment using their discretion to improve display compatibility. * * <p>With this property set to {@code false}, the system will not apply the force rotation * treatment. * * <p><b>Syntax:</b> * <pre> * <activity> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION" * android:value="true|false"/> * </activity> * </pre> * * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; /** * Activity level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be excluded * from the activity "refresh" after the camera compatibility force rotation treatment. * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural * orientation of the device and set opposite to natural orientation for a landscape app * window. Mismatch between them can lead to camera issues like sideways or stretched * viewfinder since this is one of the strongest assumptions that apps make when they implement * camera previews. Since app and natural display orientations aren't guaranteed to match, the * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to * camera and is removed once camera is closed. * * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed -> * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle * (if overridden, see {@link #PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE} for context). * This allows to clear cached values in apps (e.g. display or camera rotation) that influence * camera preview and can lead to sideways or stretching issues persisting even after force * rotation. * * <p>The camera compatibility can be enabled by device manufacturers on the displays that have * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> * for more details). * * <p>With this property set to {@code true} or unset, the system may "refresh" activity after * the force rotation treatment. Device manufacturers can exclude packages from the "refresh" * using their discretion to improve display compatibility. * * <p>With this property set to {@code false}, the system will not "refresh" activity after the * force rotation treatment. * * <p><b>Syntax:</b> * <pre> * <activity> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH" * android:value="true|false"/> * </activity> * </pre> * * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH"; /** * Activity level {@link android.content.pm.PackageManager.Property PackageManager * .Property} for an app to inform the system that the activity should be or shouldn't be * "refreshed" after the camera compatibility force rotation treatment using "paused -> * resumed" cycle rather than "stopped -> resumed". * * <p>The camera compatibility treatment aligns orientations of portrait app window and natural * orientation of the device and set opposite to natural orientation for a landscape app * window. Mismatch between them can lead to camera issues like sideways or stretched * viewfinder since this is one of the strongest assumptions that apps make when they implement * camera previews. Since app and natural display orientations aren't guaranteed to match, the * rotation can cause letterboxing. The forced rotation is triggered as soon as app opens to * camera and is removed once camera is closed. * * <p>Force rotation is followed by the "Refresh" of the activity by going through "resumed -> * ... -> stopped -> ... -> resumed" cycle (by default) or "resumed -> paused -> resumed" cycle * (if overridden by device manufacturers or using this property). This allows to clear cached * values in apps (e.g., display or camera rotation) that influence camera preview and can lead * to sideways or stretching issues persisting even after force rotation. * * <p>The camera compatibility can be enabled by device manufacturers on the displays that have * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed * orientation, see <a href="https://developer.android.com/guide/practices/enhanced-letterboxing">Enhanced letterboxing</a> * for more details). * * <p>Device manufacturers can override packages to "refresh" via "resumed -> paused -> resumed" * cycle using their discretion to improve display compatibility. * * <p>With this property set to {@code true}, the system will "refresh" activity after the * force rotation treatment using "resumed -> paused -> resumed" cycle. * * <p>With this property set to {@code false}, the system will not "refresh" activity after the * force rotation treatment using "resumed -> paused -> resumed" cycle even if the device * manufacturer adds the corresponding override. * * <p><b>Syntax:</b> * <pre> * <activity> * <property * android:name="android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE" * android:value="true|false"/> * </activity> * </pre> * * @hide */ // TODO(b/263984287): Make this public API. String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; /** * @hide */ Loading
services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +9 −4 Original line number Diff line number Diff line Loading @@ -203,8 +203,11 @@ final class DisplayRotationCompatPolicy { || !shouldRefreshActivity(activity, newConfig, lastReportedConfig)) { return; } boolean cycleThroughStop = mWmService.mLetterboxConfiguration .isCameraCompatRefreshCycleThroughStopEnabled(); boolean cycleThroughStop = mWmService.mLetterboxConfiguration .isCameraCompatRefreshCycleThroughStopEnabled() && !activity.mLetterboxUiController .shouldRefreshActivityViaPauseForCameraCompat(); try { activity.mLetterboxUiController.setIsRefreshAfterRotationRequested(true); ProtoLog.v(WM_DEBUG_STATES, Loading Loading @@ -255,7 +258,8 @@ final class DisplayRotationCompatPolicy { Configuration lastReportedConfig) { return newConfig.windowConfiguration.getDisplayRotation() != lastReportedConfig.windowConfiguration.getDisplayRotation() && isTreatmentEnabledForActivity(activity); && isTreatmentEnabledForActivity(activity) && activity.mLetterboxUiController.shouldRefreshActivityForCameraCompat(); } /** Loading Loading @@ -294,7 +298,8 @@ final class DisplayRotationCompatPolicy { // handle dynamic changes so we shouldn't force rotate them. && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED && mCameraIdPackageBiMap.containsPackageName(activity.packageName); && mCameraIdPackageBiMap.containsPackageName(activity.packageName) && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } private synchronized void notifyCameraOpened( Loading
services/core/java/com/android/server/wm/LetterboxUiController.java +148 −9 Original line number Diff line number Diff line Loading @@ -17,12 +17,18 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH; import static android.content.pm.ActivityInfo.OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE; import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; 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_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; Loading Loading @@ -132,6 +138,15 @@ final class LetterboxUiController { @Nullable private Letterbox mLetterbox; @Nullable private final Boolean mBooleanPropertyCameraCompatAllowForceRotation; @Nullable private final Boolean mBooleanPropertyCameraCompatAllowRefresh; @Nullable private final Boolean mBooleanPropertyCameraCompatEnableRefreshViaPause; // Whether activity "refresh" was requested but not finished in // ActivityRecord#activityResumedLocked following the camera compat force rotation in // DisplayRotationCompatPolicy. Loading @@ -154,8 +169,33 @@ final class LetterboxUiController { readComponentProperty(packageManager, mActivityRecord.packageName, mLetterboxConfiguration::isPolicyForIgnoringRequestedOrientationEnabled, PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION); mBooleanPropertyCameraCompatAllowForceRotation = readComponentProperty(packageManager, mActivityRecord.packageName, () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION); mBooleanPropertyCameraCompatAllowRefresh = readComponentProperty(packageManager, mActivityRecord.packageName, () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH); mBooleanPropertyCameraCompatEnableRefreshViaPause = readComponentProperty(packageManager, mActivityRecord.packageName, () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); } /** * Reads a {@link Boolean} component property fot a given {@code packageName} and a {@code * propertyName}. Returns {@code null} if {@code gatingCondition} is {@code false} or if the * property isn't specified for the package. * * <p>Return value is {@link Boolean} rather than {@code boolean} so we can know when the * property is unset. Particularly, when this returns {@code null}, {@link * #shouldEnableWithOverrideAndProperty} will check the value of override for the final * decision. */ @Nullable private static Boolean readComponentProperty(PackageManager packageManager, String packageName, BooleanSupplier gatingCondition, String propertyName) { Loading Loading @@ -210,15 +250,11 @@ final class LetterboxUiController { * </ul> */ boolean shouldIgnoreRequestedOrientation(@ScreenOrientation int requestedOrientation) { if (!mLetterboxConfiguration.isPolicyForIgnoringRequestedOrientationEnabled()) { return false; } if (Boolean.FALSE.equals(mBooleanPropertyIgnoreRequestedOrientation)) { return false; } if (!Boolean.TRUE.equals(mBooleanPropertyIgnoreRequestedOrientation) && !mActivityRecord.info.isChangeEnabled( OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION)) { if (!shouldEnableWithOverrideAndProperty( /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, mBooleanPropertyIgnoreRequestedOrientation)) { return false; } if (mIsRelauchingAfterRequestedOrientationChanged) { Loading Loading @@ -262,6 +298,109 @@ final class LetterboxUiController { mIsRefreshAfterRotationRequested = isRequested; } /** * Whether activity is eligible for activity "refresh" after camera compat force rotation * treatment. See {@link DisplayRotationCompatPolicy} for context. * * <p>This treatment is enabled when the following conditions are met: * <ul> * <li>Flag gating the camera compat treatment is enabled. * <li>Activity isn't opted out by the device manufacturer with override or by the app * developers with the component property. * </ul> */ boolean shouldRefreshActivityForCameraCompat() { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH, mBooleanPropertyCameraCompatAllowRefresh); } /** * Whether activity should be "refreshed" after the camera compat force rotation treatment * using the "resumed -> paused -> resumed" cycle rather than the "resumed -> ... -> stopped * -> ... -> resumed" cycle. See {@link DisplayRotationCompatPolicy} for context. * * <p>This treatment is enabled when the following conditions are met: * <ul> * <li>Flag gating the camera compat treatment is enabled. * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle isn't disabled with the * component property by the app developers. * <li>Activity "refresh" via "resumed -> paused -> resumed" cycle is enabled by the device * manufacturer with override / by the app developers with the component property. * </ul> */ boolean shouldRefreshActivityViaPauseForCameraCompat() { return shouldEnableWithOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, mBooleanPropertyCameraCompatEnableRefreshViaPause); } /** * Whether activity is eligible for camera compat force rotation treatment. See {@link * DisplayRotationCompatPolicy} for context. * * <p>This treatment is enabled when the following conditions are met: * <ul> * <li>Flag gating the camera compat treatment is enabled. * <li>Activity isn't opted out by the device manufacturer with override or by the app * developers with the component property. * </ul> */ boolean shouldForceRotateForCameraCompat() { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION, mBooleanPropertyCameraCompatAllowForceRotation); } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} isn't {@code false} * <li>OEM didn't opt out with a {@code overrideChangeId} override * <li>App developers didn't opt out with a component {@code property} * </ul> * * <p>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 shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition, long overrideChangeId, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } return !Boolean.FALSE.equals(property) && !mActivityRecord.info.isChangeEnabled(overrideChangeId); } /** * Returns {@code true} when the following conditions are met: * <ul> * <li>{@code gatingCondition} isn't {@code false} * <li>App developers didn't opt out with a component {@code property} * <li>App developers opted in with a component {@code property} or an OEM opted in with a * component {@code property} * </ul> * * <p>This is used for the treatments that are enabled only on per-app basis. */ private boolean shouldEnableWithOverrideAndProperty(BooleanSupplier gatingCondition, long overrideChangeId, Boolean property) { if (!gatingCondition.getAsBoolean()) { return false; } if (Boolean.FALSE.equals(property)) { return false; } return Boolean.TRUE.equals(property) || mActivityRecord.info.isChangeEnabled(overrideChangeId); } boolean hasWallpaperBackgroundForLetterbox() { return mShowWallpaperForLetterboxBackground; } Loading
services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java +40 −1 Original line number Diff line number Diff line Loading @@ -154,6 +154,18 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { assertNoForceRotationOrRefresh(); } @Test public void testTreatmentDisabledPerApp_noForceRotationOrRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); when(mActivity.mLetterboxUiController.shouldForceRotateForCameraCompat()) .thenReturn(false); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); assertNoForceRotationOrRefresh(); } @Test public void testMultiWindowMode_returnUnspecified_noForceRotationOrRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); Loading Loading @@ -327,7 +339,21 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { } @Test public void testOnActivityConfigurationChanging_refreshDisabled_noRefresh() throws Exception { public void testOnActivityConfigurationChanging_refreshDisabledViaFlag_noRefresh() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); when(mActivity.mLetterboxUiController.shouldRefreshActivityForCameraCompat()) .thenReturn(false); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); assertActivityRefreshRequested(/* refreshRequested */ false); } @Test public void testOnActivityConfigurationChanging_refreshDisabledPerApp_noRefresh() throws Exception { when(mLetterboxConfiguration.isCameraCompatRefreshEnabled()).thenReturn(false); configureActivity(SCREEN_ORIENTATION_PORTRAIT); Loading Loading @@ -362,6 +388,19 @@ public final class DisplayRotationCompatPolicyTests extends WindowTestsBase { assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); } @Test public void testOnActivityConfigurationChanging_cycleThroughStopDisabledForApp() throws Exception { configureActivity(SCREEN_ORIENTATION_PORTRAIT); when(mActivity.mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat()) .thenReturn(true); mCameraAvailabilityCallback.onCameraOpened(CAMERA_ID_1, TEST_PACKAGE_1); callOnActivityConfigurationChanging(mActivity, /* isDisplayRotationChanging */ true); assertActivityRefreshRequested(/* refreshRequested */ true, /* cycleThroughStop */ false); } private void configureActivity(@ScreenOrientation int activityOrientation) { configureActivityAndDisplay(activityOrientation, ORIENTATION_PORTRAIT); } Loading