Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 338aeabc authored by Mariia Sandrikova's avatar Mariia Sandrikova
Browse files

Per-app controls for using landscape display orientation

OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION fixes display orientation to landscape natural orientation when the following conditions are met:
- Task is in fullscreen
- Opt-out component property PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE isn't enabled
- ignoreOrientationRequest isn’t enabled for the display
- Natural orientation of the display is landscape

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 DisplayRotationCompatPolicy but not all of them can handle dynamic rotation and thus can benefit from this override.

Also, start caching other overrides in LetterboxUiController

Test: atest WmTests:LetterboxUiControllerTest
Fix: 255940284
Change-Id: I2a8688e7a033b30ea83db827679c729feaac1c9c
parent fdcb4c9f
Loading
Loading
Loading
Loading
+23 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>This override is applicable only when natural orientation of the device is
     * landscape and display ignores orientation requestes.
     *
     * <p>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.
+45 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>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.
     *
     * <p>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 <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 wiil use landscape display
     * orientation when the following conditions are met:
     * <ul>
     *     <li>Natural orientation of the display is landscape
     *     <li>ignoreOrientationRequest display setting is enabled
     *     <li>Activity is fullscreen.
     *     <li>Device manufacturer enabled the treatment.
     * </ul>
     *
     * <p>With this property set to {@code false}, device manufactured per-app override for
     * display orientation won't be applied.
     *
     * <p><b>Syntax:</b>
     * <pre>
     * &lt;activity&gt;
     *   &lt;property
     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"
     *     android:value="true|false"/&gt;
     * &lt;/activity&gt;
     * </pre>
     *
     * @hide
     */
    // TODO(b/263984287): Make this public API.
    String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE =
            "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";

    /**
     * @hide
     */
+6 −0
Original line number Diff line number Diff line
@@ -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",
+10 −0
Original line number Diff line number Diff line
@@ -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;
@@ -2727,6 +2728,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,
+91 −12
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>This treatment is enabled when the following conditions are met:
     * <ul>
     *     <li>Opt-out component property isn't enabled
     *     <li>Opt-in per-app override is enabled
     *     <li>Task is in fullscreen.
     *     <li>{@link DisplayContent#getIgnoreOrientationRequest} is enabled
     *     <li>Natural orientation of the display is landscape.
     * </ul>
     */
    boolean shouldUseDisplayLandscapeNaturalOrientation() {
        return shouldEnableWithOptInOverrideAndOptOutProperty(
                /* gatingCondition */ () -> mActivityRecord.mDisplayContent != null
                        && mActivityRecord.getTask() != null
                        && mActivityRecord.mDisplayContent.getIgnoreOrientationRequest()
                        && !mActivityRecord.getTask().inMultiWindowMode()
                        && mActivityRecord.mDisplayContent.getNaturalOrientation()
                                == ORIENTATION_LANDSCAPE,
                mIsOverrideUseDisplayLandscapeNaturalOrientationEnabled,
                mBooleanPropertyAllowDisplayOrientationOverride);
    }

    @ScreenOrientation
    int overrideOrientationIfNeeded(@ScreenOrientation int candidate) {
        if (Boolean.FALSE.equals(mBooleanPropertyAllowOrientationOverride)) {
        if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) {
            return candidate;
        }

@@ -394,7 +454,7 @@ final class LetterboxUiController {
        return shouldEnableWithOptOutOverrideAndProperty(
                /* gatingCondition */ () -> mLetterboxConfiguration
                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
                OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH,
                mIsOverrideCameraCompatDisableRefreshEnabled,
                mBooleanPropertyCameraCompatAllowRefresh);
    }

@@ -416,7 +476,7 @@ final class LetterboxUiController {
        return shouldEnableWithOverrideAndProperty(
                /* gatingCondition */ () -> mLetterboxConfiguration
                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
                OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE,
                mIsOverrideCameraCompatEnableRefreshViaPauseEnabled,
                mBooleanPropertyCameraCompatEnableRefreshViaPause);
    }

@@ -435,7 +495,7 @@ final class LetterboxUiController {
        return shouldEnableWithOptOutOverrideAndProperty(
                /* gatingCondition */ () -> mLetterboxConfiguration
                        .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true),
                OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION,
                mIsOverrideCameraCompatDisableForceRotationEnabled,
                mBooleanPropertyCameraCompatAllowForceRotation);
    }

@@ -447,7 +507,7 @@ final class LetterboxUiController {
     * 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>OEM didn't opt out with a per-app override
     *     <li>App developers didn't opt out with a component {@code property}
     * </ul>
     *
@@ -455,11 +515,30 @@ final class LetterboxUiController {
     * disabled on per-app basis by OEMs or app developers.
     */
    private boolean shouldEnableWithOptOutOverrideAndProperty(BooleanSupplier gatingCondition,
            long overrideChangeId, Boolean property) {
            boolean isOverrideChangeEnabled, Boolean property) {
        if (!gatingCondition.getAsBoolean()) {
            return false;
        }
        return !FALSE.equals(property) && !isOverrideChangeEnabled;
    }

    /**
     * Returns {@code true} when the following conditions are met:
     * <ul>
     *     <li>{@code gatingCondition} isn't {@code false}
     *     <li>OEM did opt in with a per-app 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 shouldEnableWithOptInOverrideAndOptOutProperty(BooleanSupplier gatingCondition,
            boolean isOverrideChangeEnabled, Boolean property) {
        if (!gatingCondition.getAsBoolean()) {
            return false;
        }
        return !Boolean.FALSE.equals(property) && !isCompatChangeEnabled(overrideChangeId);
        return !FALSE.equals(property) && isOverrideChangeEnabled;
    }

    /**
@@ -468,20 +547,20 @@ final class LetterboxUiController {
     *     <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
     *     {@code overrideChangeId} override
     *     per-app override
     * </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) {
            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() {
Loading