From fdcb4c9fd47712d7ce4cc8c939aaf5ca71e6a8c3 Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Mon, 16 Jan 2023 02:34:36 +0000 Subject: [PATCH] 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