From fdcb4c9fd47712d7ce4cc8c939aaf5ca71e6a8c3 Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Mon, 16 Jan 2023 02:34:36 +0000 Subject: [PATCH 1/2] Add orientation per-app controls. Overrides for device manufacturers: - OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT override orientation to SCREEN_ORIENTATION_PORTRAIT - OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR override orientation to SCREEN_ORIENTATION_NOSENSOR - OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE override orientation to SCREEN_ORIENTATION_REVERSE_LANDSCAPE - OVERRIDE_ANY_ORIENTATION allows OVERRIDE_*_ORIENTATION_TO_* to override any orientation. PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE allows app developers to opt-out from per-app overrides above. Test: atest WmTests:LetterboxUiControllerTest Fix: 255940284, 265452344, 265451093, 265464455, 266124927 Merged-In: I6d83170632a04e3d60461f37f13acc1de5b6a6cb Change-Id: I6d83170632a04e3d60461f37f13acc1de5b6a6cb --- .../java/android/content/pm/ActivityInfo.java | 65 ++++++++++++- core/java/android/view/WindowManager.java | 26 ++++++ .../server/wm/ActivityClientController.java | 3 +- .../com/android/server/wm/ActivityRecord.java | 78 +++++++++++----- .../com/android/server/wm/DisplayArea.java | 5 +- .../com/android/server/wm/DisplayContent.java | 14 ++- .../android/server/wm/DisplayRotation.java | 5 +- .../wm/DisplayRotationCompatPolicy.java | 4 +- .../server/wm/LetterboxUiController.java | 91 +++++++++++++++++-- .../android/server/wm/WindowContainer.java | 59 ++++++++---- .../server/wm/DisplayContentTests.java | 2 +- .../server/wm/LetterboxUiControllerTest.java | 83 +++++++++++++++++ .../wm/RecentsAnimationControllerTest.java | 2 +- .../server/wm/WindowContainerTests.java | 2 +- 14 files changed, 373 insertions(+), 66 deletions(-) diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 2e3b5d286138f..c384fbce75501 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1187,6 +1187,56 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { @Overridable public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L; + // Compat framework that per-app overrides rely on only supports booleans. That's why we have + // multiple OVERRIDE_*_ORIENTATION_* change ids below instead of just one override with + // the integer value. + + /** + * Enables {@link #SCREEN_ORIENTATION_PORTRAIT}. Unless OVERRIDE_ANY_ORIENTATION + * is enabled, this override is used only when no other fixed orientation was specified by the + * activity. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; + + /** + * Enables {@link #SCREEN_ORIENTATION_NOSENSOR}. Unless OVERRIDE_ANY_ORIENTATION + * is enabled, this override is used only when no other fixed orientation was specified by the + * activity. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR = 265451093L; + + /** + * Enables {@link #SCREEN_ORIENTATION_REVERSE_LANDSCAPE}. Unless OVERRIDE_ANY_ORIENTATION + * is enabled, this override is used only when activity specify landscape orientation. + * This can help apps that assume that landscape display orientation corresponds to {@link + * android.view.Surface#ROTATION_90}, while on some devices it can be {@link + * android.view.Surface#ROTATION_270}. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L; + + /** + * When enabled, allows OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE, + * OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR and OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT + * to override any orientation requested by the activity. + * @hide + */ + @ChangeId + @Disabled + @Overridable + public static final long OVERRIDE_ANY_ORIENTATION = 265464455L; + /** * Compares activity window layout min width/height with require space for multi window to * determine if it can be put into multi window mode. @@ -1405,8 +1455,19 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { * @hide */ public boolean isFixedOrientation() { - return isFixedOrientationLandscape() || isFixedOrientationPortrait() - || screenOrientation == SCREEN_ORIENTATION_LOCKED; + return isFixedOrientation(screenOrientation); + } + + /** + * Returns true if the passed activity's orientation is fixed. + * @hide + */ + public static boolean isFixedOrientation(@ScreenOrientation int orientation) { + return orientation == SCREEN_ORIENTATION_LOCKED + // Orientation is fixed to natural display orientation + || orientation == SCREEN_ORIENTATION_NOSENSOR + || isFixedOrientationLandscape(orientation) + || isFixedOrientationPortrait(orientation); } /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index a37c24499aff0..1e1e2b9c85d61 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -989,6 +989,32 @@ public interface WindowManager extends ViewManager { String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE"; + /** + * 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 + * compatibility override for orientation set by the device manufacturer. + * + *

With this property set to {@code true} or unset, device manufacturers can override + * orientation for the activity using their discretion to improve display compatibility. + * + *

With this property set to {@code false}, device manufactured per-app override for + * orientation won't be applied. + * + *

Syntax: + *

+     * <activity>
+     *   <property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"
+     *     android:value="true|false"/>
+     * </activity>
+     * 
+ * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = + "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE"; + /** * @hide */ diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index 725254513519e..7489f80946ebb 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -709,7 +709,8 @@ class ActivityClientController extends IActivityClientController.Stub { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); return r != null - ? r.getRequestedOrientation() : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; + ? r.getOverrideOrientation() + : ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 6b01a7726a436..41721cea218bd 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1165,8 +1165,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "mVoiceInteraction=true"); } pw.print(prefix); pw.print("mOccludesParent="); pw.println(mOccludesParent); - pw.print(prefix); pw.print("mOrientation="); - pw.println(ActivityInfo.screenOrientationToString(mOrientation)); + pw.print(prefix); pw.print("overrideOrientation="); + pw.println(ActivityInfo.screenOrientationToString(getOverrideOrientation())); + pw.print(prefix); pw.print("requestedOrientation="); + pw.println(ActivityInfo.screenOrientationToString(super.getOverrideOrientation())); pw.println(prefix + "mVisibleRequested=" + mVisibleRequested + " mVisible=" + mVisible + " mClientVisible=" + isClientVisible() + ((mDeferHidingClient) ? " mDeferHidingClient=" + mDeferHidingClient : "") @@ -1964,6 +1966,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A new ComponentName(info.packageName, info.targetActivity); } + // Don't move below setActivityType since it triggers onConfigurationChange -> + // resolveOverrideConfiguration that requires having mLetterboxUiController initialised. + // Don't move below setOrientation(info.screenOrientation) since it triggers + // getOverrideOrientation that requires having mLetterboxUiController + // initialised. + mLetterboxUiController = new LetterboxUiController(mWmService, this); + mCameraCompatControlEnabled = mWmService.mContext.getResources() + .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); + mTargetSdk = info.applicationInfo.targetSdkVersion; mShowForAllUsers = (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0; setOrientation(info.screenOrientation); @@ -2084,12 +2095,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A launchMode = aInfo.launchMode; - // Don't move below setActivityType since it triggers onConfigurationChange -> - // resolveOverrideConfiguration that requires having mLetterboxUiController initialised. - mLetterboxUiController = new LetterboxUiController(mWmService, this); - mCameraCompatControlEnabled = mWmService.mContext.getResources() - .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled); - setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord); immersive = (aInfo.flags & FLAG_IMMERSIVE) != 0; @@ -2486,7 +2491,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (topAttached != null) { if (topAttached.isSnapshotCompatible(snapshot) // This trampoline must be the same rotation. - && mDisplayContent.getDisplayRotation().rotationForOrientation(mOrientation, + && mDisplayContent.getDisplayRotation().rotationForOrientation( + getOverrideOrientation(), mDisplayContent.getRotation()) == snapshot.getRotation()) { return STARTING_WINDOW_TYPE_SNAPSHOT; } @@ -7704,13 +7710,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLetterboxUiController.getInheritedOrientation(); } } - if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) { + if (task != null && getOverrideOrientation() == SCREEN_ORIENTATION_BEHIND) { // We use Task here because we want to be consistent with what happens in // multi-window mode where other tasks orientations are ignored. final ActivityRecord belowCandidate = task.getActivity( - a -> a.mOrientation != SCREEN_ORIENTATION_UNSET && !a.finishing - && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, this, - false /* includeBoundary */, true /* traverseTopToBottom */); + a -> a.canDefineOrientationForActivitiesAbove() /* callback */, + this /* boundary */, false /* includeBoundary */, + true /* traverseTopToBottom */); if (belowCandidate != null) { return belowCandidate.getRequestedConfigurationOrientation(forDisplay); } @@ -7718,6 +7724,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return super.getRequestedConfigurationOrientation(forDisplay); } + /** + * Whether this activity can be used as an orientation source for activities above with + * {@link SCREEN_ORIENTATION_BEHIND}. + */ + boolean canDefineOrientationForActivitiesAbove() { + if (finishing) { + return false; + } + final int overrideOrientation = getOverrideOrientation(); + return overrideOrientation != SCREEN_ORIENTATION_UNSET + && overrideOrientation != SCREEN_ORIENTATION_BEHIND; + } + @Override void onCancelFixedRotationTransform(int originalDisplayRotation) { if (this != mDisplayContent.getLastOrientationSource()) { @@ -7744,7 +7763,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - void setRequestedOrientation(int requestedOrientation) { + void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) { if (mLetterboxUiController.shouldIgnoreRequestedOrientation(requestedOrientation)) { return; } @@ -7789,7 +7808,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Allow app to specify orientation regardless of its visibility state if the current // candidate want us to use orientation behind. I.e. the visible app on-top of this one // wants us to use the orientation of the app behind it. - return mOrientation; + return getOverrideOrientation(); } // The {@link ActivityRecord} should only specify an orientation when it is not closing. @@ -7797,15 +7816,31 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // task being started in the wrong orientation during the transition. if (!getDisplayContent().mClosingApps.contains(this) && (isVisibleRequested() || getDisplayContent().mOpeningApps.contains(this))) { - return mOrientation; + return getOverrideOrientation(); } return SCREEN_ORIENTATION_UNSET; } - /** Returns the app's preferred orientation regardless of its currently visibility state. */ + /** + * Returns the app's preferred orientation regardless of its current visibility state taking + * into account orientation per-app overrides applied by the device manufacturers. + */ + @Override + protected int getOverrideOrientation() { + return mLetterboxUiController.overrideOrientationIfNeeded(super.getOverrideOrientation()); + } + + /** + * Returns the app's preferred orientation regardless of its currently visibility state. This + * is used to return a requested value to an app if they call {@link + * android.app.Activity#getRequestedOrientation} since {@link #getOverrideOrientation} value + * with override can confuse an app if it's different from what they requested with {@link + * android.app.Activity#setRequestedOrientation}. + */ + @ActivityInfo.ScreenOrientation int getRequestedOrientation() { - return mOrientation; + return super.getOverrideOrientation(); } /** @@ -8357,8 +8392,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If orientation is respected when insets are applied, then stableBounds will be empty. boolean orientationRespectedWithInsets = orientationRespectedWithInsets(parentBounds, stableBounds); - if (orientationRespectedWithInsets - && handlesOrientationChangeFromDescendant(mOrientation)) { + if (orientationRespectedWithInsets && handlesOrientationChangeFromDescendant( + getOverrideOrientation())) { // No need to letterbox because of fixed orientation. Display will handle // fixed-orientation requests and a display rotation is enough to respect requested // orientation with insets applied. @@ -9003,7 +9038,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY) - && !ActivityInfo.isFixedOrientationPortrait(getRequestedOrientation())) { + && !ActivityInfo.isFixedOrientationPortrait( + getOverrideOrientation())) { return info.getMinAspectRatio(); } diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java index af5bd14baf312..fad2ddadc88ff 100644 --- a/services/core/java/com/android/server/wm/DisplayArea.java +++ b/services/core/java/com/android/server/wm/DisplayArea.java @@ -93,7 +93,7 @@ public class DisplayArea extends WindowContainer { DisplayArea(WindowManagerService wms, Type type, String name, int featureId) { super(wms); // TODO(display-area): move this up to ConfigurationContainer - mOrientation = SCREEN_ORIENTATION_UNSET; + setOverrideOrientation(SCREEN_ORIENTATION_UNSET); mType = type; mName = name; mFeatureId = featureId; @@ -165,7 +165,8 @@ public class DisplayArea extends WindowContainer { // If this is set to ignore the orientation request, we don't propagate descendant // orientation request. final int orientation = requestingContainer != null - ? requestingContainer.mOrientation : SCREEN_ORIENTATION_UNSET; + ? requestingContainer.getOverrideOrientation() + : SCREEN_ORIENTATION_UNSET; return !getIgnoreOrientationRequest(orientation) && super.onDescendantOrientationChanged(requestingContainer); } diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 9af1b2bd40c69..b4aa6213e2b0b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -1572,7 +1572,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // If display rotation class tells us that it doesn't consider app requested orientation, // this display won't rotate just because of an app changes its requested orientation. Thus // it indicates that this display chooses not to handle this request. - final int orientation = requestingContainer != null ? requestingContainer.mOrientation + final int orientation = requestingContainer != null + ? requestingContainer.getOverrideOrientation() : SCREEN_ORIENTATION_UNSET; final boolean handled = handlesOrientationChangeFromDescendant(orientation); if (config == null) { @@ -1698,14 +1699,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp if (mTransitionController.useShellTransitionsRotation()) { return ROTATION_UNDEFINED; } + final int activityOrientation = r.getOverrideOrientation(); if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM - || getIgnoreOrientationRequest(r.mOrientation)) { + || getIgnoreOrientationRequest(activityOrientation)) { return ROTATION_UNDEFINED; } - if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + if (activityOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + // TODO(b/266280737): Use ActivityRecord#canDefineOrientationForActivitiesAbove final ActivityRecord nextCandidate = getActivity( - a -> a.mOrientation != SCREEN_ORIENTATION_UNSET - && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND, + a -> a.getOverrideOrientation() != SCREEN_ORIENTATION_UNSET + && a.getOverrideOrientation() + != ActivityInfo.SCREEN_ORIENTATION_BEHIND, r, false /* includeBoundary */, true /* traverseTopToBottom */); if (nextCandidate != null) { r = nextCandidate; diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index e6d8b3db45647..306b0f7bbe372 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1843,8 +1843,9 @@ public class DisplayRotation { if (source != null) { mLastOrientationSource = source.toString(); final WindowState w = source.asWindowState(); - mSourceOrientation = - w != null ? w.mAttrs.screenOrientation : source.mOrientation; + mSourceOrientation = w != null + ? w.mAttrs.screenOrientation + : source.getOverrideOrientation(); } else { mLastOrientationSource = null; mSourceOrientation = SCREEN_ORIENTATION_UNSET; diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java index c6037dab65685..3ffb2fa7eaadf 100644 --- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java @@ -296,8 +296,8 @@ final class DisplayRotationCompatPolicy { && activity.getRequestedConfigurationOrientation() != ORIENTATION_UNDEFINED // "locked" and "nosensor" values are often used by camera apps that can't // handle dynamic changes so we shouldn't force rotate them. - && activity.getRequestedOrientation() != SCREEN_ORIENTATION_NOSENSOR - && activity.getRequestedOrientation() != SCREEN_ORIENTATION_LOCKED + && activity.getOverrideOrientation() != SCREEN_ORIENTATION_NOSENSOR + && activity.getOverrideOrientation() != SCREEN_ORIENTATION_LOCKED && mCameraIdPackageBiMap.containsPackageName(activity.packageName) && activity.mLetterboxUiController.shouldForceRotateForCameraCompat(); } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 73d1ff9d27d2d..977b330716cd4 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,11 +17,20 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; 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.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.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.pm.ActivityInfo.isFixedOrientation; +import static android.content.pm.ActivityInfo.isFixedOrientationLandscape; import static android.content.pm.ActivityInfo.screenOrientationToString; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.content.res.Configuration.ORIENTATION_PORTRAIT; @@ -29,6 +38,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_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; @@ -111,6 +121,20 @@ final class LetterboxUiController { */ private final float mExpandedTaskBarHeight; + // TODO(b/265576778): Cache other overrides as well. + + // Corresponds to OVERRIDE_ANY_ORIENTATION + private final boolean mIsOverrideAnyOrientationEnabled; + // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT + private final boolean mIsOverrideToPortraitOrientationEnabled; + // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR + private final boolean mIsOverrideToNosensorOrientationEnabled; + // Corresponds to OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE + private final boolean mIsOverrideToReverseLandscapeOrientationEnabled; + + @Nullable + private final Boolean mBooleanPropertyAllowOrientationOverride; + /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not @@ -193,6 +217,19 @@ final class LetterboxUiController { mExpandedTaskBarHeight = getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); + + mBooleanPropertyAllowOrientationOverride = + readComponentProperty(packageManager, mActivityRecord.packageName, + /* gatingCondition */ null, + PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE); + + mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION); + mIsOverrideToPortraitOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT); + mIsOverrideToReverseLandscapeOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE); + mIsOverrideToNosensorOrientationEnabled = + isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR); } /** @@ -207,8 +244,8 @@ final class LetterboxUiController { */ @Nullable private static Boolean readComponentProperty(PackageManager packageManager, String packageName, - BooleanSupplier gatingCondition, String propertyName) { - if (!gatingCondition.getAsBoolean()) { + @Nullable BooleanSupplier gatingCondition, String propertyName) { + if (gatingCondition != null && !gatingCondition.getAsBoolean()) { return null; } try { @@ -307,6 +344,41 @@ final class LetterboxUiController { mIsRefreshAfterRotationRequested = isRequested; } + @ScreenOrientation + int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { + if (Boolean.FALSE.equals(mBooleanPropertyAllowOrientationOverride)) { + return candidate; + } + + if (mIsOverrideToReverseLandscapeOrientationEnabled + && (isFixedOrientationLandscape(candidate) || mIsOverrideAnyOrientationEnabled)) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_REVERSE_LANDSCAPE)); + return SCREEN_ORIENTATION_REVERSE_LANDSCAPE; + } + + if (!mIsOverrideAnyOrientationEnabled && isFixedOrientation(candidate)) { + return candidate; + } + + if (mIsOverrideToPortraitOrientationEnabled) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_PORTRAIT)); + return SCREEN_ORIENTATION_PORTRAIT; + } + + if (mIsOverrideToNosensorOrientationEnabled) { + Slog.w(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for " + + mActivityRecord + " is overridden to " + + screenOrientationToString(SCREEN_ORIENTATION_NOSENSOR)); + return SCREEN_ORIENTATION_NOSENSOR; + } + + return candidate; + } + /** * Whether activity is eligible for activity "refresh" after camera compat force rotation * treatment. See {@link DisplayRotationCompatPolicy} for context. @@ -367,6 +439,10 @@ final class LetterboxUiController { mBooleanPropertyCameraCompatAllowForceRotation); } + private boolean isCompatChangeEnabled(long overrideChangeId) { + return mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + /** * Returns {@code true} when the following conditions are met: *
    @@ -383,8 +459,7 @@ final class LetterboxUiController { if (!gatingCondition.getAsBoolean()) { return false; } - return !Boolean.FALSE.equals(property) - && !mActivityRecord.info.isChangeEnabled(overrideChangeId); + return !Boolean.FALSE.equals(property) && !isCompatChangeEnabled(overrideChangeId); } /** @@ -393,7 +468,7 @@ final class LetterboxUiController { *
  • {@code gatingCondition} isn't {@code false} *
  • App developers didn't opt out with a component {@code property} *
  • App developers opted in with a component {@code property} or an OEM opted in with a - * component {@code property} + * {@code overrideChangeId} override *
* *

This is used for the treatments that are enabled only on per-app basis. @@ -406,8 +481,7 @@ final class LetterboxUiController { if (Boolean.FALSE.equals(property)) { return false; } - return Boolean.TRUE.equals(property) - || mActivityRecord.info.isChangeEnabled(overrideChangeId); + return Boolean.TRUE.equals(property) || isCompatChangeEnabled(overrideChangeId); } boolean hasWallpaperBackgroundForLetterbox() { @@ -1224,7 +1298,8 @@ final class LetterboxUiController { // To avoid wrong behaviour, we're not forcing orientation for activities with not // fixed orientation (e.g. permission dialogs). return hasInheritedLetterboxBehavior() - && mActivityRecord.mOrientation != SCREEN_ORIENTATION_UNSPECIFIED; + && mActivityRecord.getOverrideOrientation() + != SCREEN_ORIENTATION_UNSPECIFIED; } float getInheritedMinAspectRatio() { diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 8bdab9c22ab78..9a20354bcf813 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -73,6 +73,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ActivityInfo; +import android.content.pm.ActivityInfo.ScreenOrientation; import android.content.res.Configuration; import android.graphics.Color; import android.graphics.Point; @@ -178,8 +179,9 @@ class WindowContainer extends ConfigurationContainer< protected final WindowList mChildren = new WindowList(); // The specified orientation for this window container. - @ActivityInfo.ScreenOrientation - protected int mOrientation = SCREEN_ORIENTATION_UNSPECIFIED; + // Shouldn't be accessed directly since subclasses can override getOverrideOrientation. + @ScreenOrientation + private int mOverrideOrientation = SCREEN_ORIENTATION_UNSPECIFIED; /** * The window container which decides its orientation since the last time @@ -1427,19 +1429,20 @@ class WindowContainer extends ConfigurationContainer< /** * Gets the configuration orientation by the requested screen orientation - * ({@link ActivityInfo.ScreenOrientation}) of this activity. + * ({@link ScreenOrientation}) of this activity. * * @return orientation in ({@link Configuration#ORIENTATION_LANDSCAPE}, * {@link Configuration#ORIENTATION_PORTRAIT}, * {@link Configuration#ORIENTATION_UNDEFINED}). */ + @ScreenOrientation int getRequestedConfigurationOrientation() { return getRequestedConfigurationOrientation(false /* forDisplay */); } /** * Gets the configuration orientation by the requested screen orientation - * ({@link ActivityInfo.ScreenOrientation}) of this activity. + * ({@link ScreenOrientation}) of this activity. * * @param forDisplay whether it is the requested config orientation for display. * If {@code true}, we may reverse the requested orientation if the root is @@ -1450,8 +1453,9 @@ class WindowContainer extends ConfigurationContainer< * {@link Configuration#ORIENTATION_PORTRAIT}, * {@link Configuration#ORIENTATION_UNDEFINED}). */ + @ScreenOrientation int getRequestedConfigurationOrientation(boolean forDisplay) { - int requestedOrientation = mOrientation; + int requestedOrientation = getOverrideOrientation(); final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { // Reverse the requested orientation if the orientation of its root is different from @@ -1461,7 +1465,7 @@ class WindowContainer extends ConfigurationContainer< // (portrait). // When an app below the DAG is requesting landscape, it should actually request the // display to be portrait, so that the DAG and the app will be in landscape. - requestedOrientation = reverseOrientation(mOrientation); + requestedOrientation = reverseOrientation(getOverrideOrientation()); } if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_NOSENSOR) { @@ -1486,7 +1490,7 @@ class WindowContainer extends ConfigurationContainer< * * @param orientation the specified orientation. */ - void setOrientation(int orientation) { + void setOrientation(@ScreenOrientation int orientation) { setOrientation(orientation, null /* requestingContainer */); } @@ -1494,17 +1498,17 @@ class WindowContainer extends ConfigurationContainer< * Sets the specified orientation of this container. It percolates this change upward along the * hierarchy to let each level of the hierarchy a chance to respond to it. * - * @param orientation the specified orientation. Needs to be one of {@link - * android.content.pm.ActivityInfo.ScreenOrientation}. + * @param orientation the specified orientation. Needs to be one of {@link ScreenOrientation}. * @param requestingContainer the container which orientation request has changed. Mostly used * to ensure it gets correct configuration. */ - void setOrientation(int orientation, @Nullable WindowContainer requestingContainer) { - if (mOrientation == orientation) { + void setOrientation(@ScreenOrientation int orientation, + @Nullable WindowContainer requestingContainer) { + if (getOverrideOrientation() == orientation) { return; } - mOrientation = orientation; + setOverrideOrientation(orientation); final WindowContainer parent = getParent(); if (parent != null) { if (getConfiguration().orientation != getRequestedConfigurationOrientation() @@ -1523,9 +1527,9 @@ class WindowContainer extends ConfigurationContainer< } } - @ActivityInfo.ScreenOrientation + @ScreenOrientation int getOrientation() { - return getOrientation(mOrientation); + return getOrientation(getOverrideOrientation()); } /** @@ -1539,7 +1543,8 @@ class WindowContainer extends ConfigurationContainer< * better match. * @return The orientation as specified by this branch or the window hierarchy. */ - int getOrientation(int candidate) { + @ScreenOrientation + int getOrientation(@ScreenOrientation int candidate) { mLastOrientationSource = null; if (!providesOrientation()) { return SCREEN_ORIENTATION_UNSET; @@ -1549,16 +1554,16 @@ class WindowContainer extends ConfigurationContainer< // specified; otherwise we prefer to use the orientation of its topmost child that has one // specified and fall back on this container's unset or unspecified value as a candidate // if none of the children have a better candidate for the orientation. - if (mOrientation != SCREEN_ORIENTATION_UNSET - && mOrientation != SCREEN_ORIENTATION_UNSPECIFIED) { + if (getOverrideOrientation() != SCREEN_ORIENTATION_UNSET + && getOverrideOrientation() != SCREEN_ORIENTATION_UNSPECIFIED) { mLastOrientationSource = this; - return mOrientation; + return getOverrideOrientation(); } for (int i = mChildren.size() - 1; i >= 0; --i) { final WindowContainer wc = mChildren.get(i); - // TODO: Maybe mOrientation should default to SCREEN_ORIENTATION_UNSET vs. + // TODO: Maybe mOverrideOrientation should default to SCREEN_ORIENTATION_UNSET vs. // SCREEN_ORIENTATION_UNSPECIFIED? final int orientation = wc.getOrientation(candidate == SCREEN_ORIENTATION_BEHIND ? SCREEN_ORIENTATION_BEHIND : SCREEN_ORIENTATION_UNSET); @@ -1589,6 +1594,20 @@ class WindowContainer extends ConfigurationContainer< return candidate; } + /** + * Returns orientation specified on this level of hierarchy without taking children into + * account, like {@link #getOrientation} does, allowing subclasses to override. See {@link + * ActivityRecord#getOverrideOrientation} for an example. + */ + @ScreenOrientation + protected int getOverrideOrientation() { + return mOverrideOrientation; + } + + protected void setOverrideOrientation(@ScreenOrientation int orientation) { + mOverrideOrientation = orientation; + } + /** * @return The deepest source which decides the orientation of this window container since the * last time {@link #getOrientation(int) was called. @@ -2635,7 +2654,7 @@ class WindowContainer extends ConfigurationContainer< final long token = proto.start(fieldId); super.dumpDebug(proto, CONFIGURATION_CONTAINER, logLevel); - proto.write(ORIENTATION, mOrientation); + proto.write(ORIENTATION, mOverrideOrientation); proto.write(VISIBLE, isVisible); writeIdentifierToProto(proto, IDENTIFIER); if (mSurfaceAnimator.isAnimating()) { diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index b0639bffe694e..dc12469960ff2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -1736,7 +1736,7 @@ public class DisplayContentTests extends WindowTestsBase { // No need to apply rotation if the display ignores orientation request. doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any()); - pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE; + pinnedActivity.setOverrideOrientation(SCREEN_ORIENTATION_LANDSCAPE); displayContent.setIgnoreOrientationRequest(true); assertEquals(WindowConfiguration.ROTATION_UNDEFINED, displayContent.rotationForActivityInDifferentOrientation(pinnedActivity)); 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 478bd855902ad..bc5e2dc65bc21 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -16,14 +16,23 @@ package com.android.server.wm; +import static android.content.pm.ActivityInfo.OVERRIDE_ANY_ORIENTATION; 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.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.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.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_ORIENTATION_OVERRIDE; import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -488,6 +497,80 @@ public class LetterboxUiControllerTest extends WindowTestsBase { return mainWindow; } + // overrideOrientationIfNeeded + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait() + throws Exception { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) + public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsNosensor() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_NOSENSOR); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR}) + public void testOverrideOrientationIfNeeded_nosensorOverride_orientationFixed_returnsUnchanged() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) + public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationPortraitOrUndefined_returnsUnchanged() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_PORTRAIT); + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED); + } + + @Test + @EnableCompatChanges({OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE}) + public void testOverrideOrientationIfNeeded_reverseLandscapeOverride_orientationLandscape_returnsReverseLandscape() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_LANDSCAPE), + SCREEN_ORIENTATION_REVERSE_LANDSCAPE); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_portraitOverride_orientationFixed_returnsUnchanged() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_NOSENSOR); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_portraitAndIgnoreFixedOverrides_returnsPortrait() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_NOSENSOR), SCREEN_ORIENTATION_PORTRAIT); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR, OVERRIDE_ANY_ORIENTATION}) + public void testOverrideOrientationIfNeeded_noSensorAndIgnoreFixedOverrides_returnsNosensor() { + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_PORTRAIT), SCREEN_ORIENTATION_NOSENSOR); + } + + @Test + @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) + public void testOverrideOrientationIfNeeded_propertyIsFalse_returnsUnchanged() + throws Exception { + mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false); + + mController = new LetterboxUiController(mWm, mActivity); + + assertEquals(mController.overrideOrientationIfNeeded( + /* candidate */ SCREEN_ORIENTATION_UNSPECIFIED), SCREEN_ORIENTATION_UNSPECIFIED); + } + private void mockThatProperty(String propertyName, boolean value) throws Exception { Property property = new Property(propertyName, /* value */ value, /* packageName */ "", /* className */ ""); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index 48084743afde6..06e3854088e92 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -468,7 +468,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mWm.setRecentsAnimationController(mController); spyOn(mDisplayContent.mFixedRotationTransitionListener); final ActivityRecord recents = mock(ActivityRecord.class); - recents.mOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; + recents.setOverrideOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); doReturn(ORIENTATION_PORTRAIT).when(recents) .getRequestedConfigurationOrientation(anyBoolean()); mDisplayContent.mFixedRotationTransitionListener.onStartRecentsAnimation(recents); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index ed7d123846622..2446fc497f045 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -1557,7 +1557,7 @@ public class WindowContainerTests extends WindowTestsBase { @Override int getOrientation() { - return getOrientation(super.mOrientation); + return getOrientation(super.getOverrideOrientation()); } @Override From 338aeabc52209b51215742c735254226168173ac Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Tue, 17 Jan 2023 00:30:36 +0000 Subject: [PATCH 2/2] Per-app controls for using landscape display orientation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../java/android/content/pm/ActivityInfo.java | 23 ++++ core/java/android/view/WindowManager.java | 45 ++++++++ data/etc/services.core.protolog.json | 6 + .../com/android/server/wm/DisplayContent.java | 10 ++ .../server/wm/LetterboxUiController.java | 103 ++++++++++++++++-- .../server/wm/LetterboxUiControllerTest.java | 65 ++++++++++- 6 files changed, 238 insertions(+), 14 deletions(-) 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: + *

    + *
  • Natural orientation of the display is landscape + *
  • ignoreOrientationRequest display setting is enabled + *
  • Activity is fullscreen. + *
  • Device manufacturer enabled the treatment. + *
+ * + *

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 b4aa6213e2b0b..1b1c305adcfbd 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; @@ -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, diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 977b330716cd4..2cf16359bd166 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: + *

    + *
  • Opt-out component property isn't enabled + *
  • Opt-in per-app override is enabled + *
  • Task is in fullscreen. + *
  • {@link DisplayContent#getIgnoreOrientationRequest} is enabled + *
  • Natural orientation of the display is landscape. + *
+ */ + 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: *
    *
  • {@code gatingCondition} isn't {@code false} - *
  • OEM didn't opt out with a {@code overrideChangeId} override + *
  • OEM didn't opt out with a per-app override *
  • App developers didn't opt out with a component {@code property} *
* @@ -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 !Boolean.FALSE.equals(property) && !isCompatChangeEnabled(overrideChangeId); + return !FALSE.equals(property) && !isOverrideChangeEnabled; + } + + /** + * Returns {@code true} when the following conditions are met: + *
    + *
  • {@code gatingCondition} isn't {@code false} + *
  • OEM did opt in with a per-app override + *
  • App developers didn't opt out with a component {@code property} + *
+ * + *

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 { *

  • {@code gatingCondition} isn't {@code false} *
  • App developers didn't opt out with a component {@code property} *
  • App developers opted in with a component {@code property} or an OEM opted in with a - * {@code overrideChangeId} override + * per-app override * * *

    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()))