diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 2e3b5d286138f..80cea55111ba4 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1187,6 +1187,79 @@ 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; + + /** + * 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. @@ -1405,8 +1478,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..e3bf2d4f436f5 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -989,6 +989,77 @@ 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"; + + /** + * Activity level {@link android.content.pm.PackageManager.Property PackageManager + * .Property} for an app to inform the system that the activity should be opted-out from the + * compatibility override that fixes display orientation to landscape natural orientation when + * an activity is fullscreen. + * + *

When this compat override is enabled and while display is fixed to the landscape natural + * orientation, the orientation requested by the activity will be still respected by bounds + * resolution logic. For instance, if an activity requests portrait orientation, then activity + * will appear in the letterbox mode for fixed orientation with the display rotated to the + * lanscape natural orientation. + * + *

The treatment is disabled by default but device manufacturers can enable the treatment + * using their discretion to improve display compatibility on the displays that have + * ignoreOrientationRequest display setting enabled (enables compatibility mode for fixed + * orientation, see Enhanced letterboxing + * for more details). + * + *

With this property set to {@code true} or unset, the system wiil use landscape display + * orientation when the following conditions are met: + *

+ * + *

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

Syntax: + *

+     * <activity>
+     *   <property
+     *     android:name="android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"
+     *     android:value="true|false"/>
+     * </activity>
+     * 
+ * + * @hide + */ + // TODO(b/263984287): Make this public API. + String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = + "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE"; + /** * @hide */ diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index f47d9c6e0c2d6..1cf819af7a243 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -3331,6 +3331,12 @@ "group": "WM_DEBUG_STATES", "at": "com\/android\/server\/wm\/TaskFragment.java" }, + "1015746067": { + "message": "Display id=%d is ignoring orientation request for %d, return %d following a per-app override for %s", + "level": "VERBOSE", + "group": "WM_DEBUG_ORIENTATION", + "at": "com\/android\/server\/wm\/DisplayContent.java" + }, "1022095595": { "message": "TaskFragment info changed name=%s", "level": "VERBOSE", diff --git a/services/core/java/com/android/server/wm/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 e87c357de4af2..5767730abd82b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -27,6 +27,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; @@ -1576,7 +1577,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) { @@ -1702,14 +1704,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; @@ -2727,6 +2732,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final int orientation = super.getOrientation(); if (!handlesOrientationChangeFromDescendant(orientation)) { + ActivityRecord topActivity = topRunningActivity(/* considerKeyguardState= */ true); + if (topActivity != null && topActivity.mLetterboxUiController + .shouldUseDisplayLandscapeNaturalOrientation()) { + ProtoLog.v(WM_DEBUG_ORIENTATION, + "Display id=%d is ignoring orientation request for %d, return %d" + + " following a per-app override for %s", + mDisplayId, orientation, SCREEN_ORIENTATION_LANDSCAPE, topActivity); + return SCREEN_ORIENTATION_LANDSCAPE; + } mLastOrientationSource = null; // Return SCREEN_ORIENTATION_UNSPECIFIED so that Display respect sensor rotation ProtoLog.v(WM_DEBUG_ORIENTATION, diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java index 3f61315ccc643..6f7ff5c21d94c 100644 --- a/services/core/java/com/android/server/wm/DisplayRotation.java +++ b/services/core/java/com/android/server/wm/DisplayRotation.java @@ -1859,8 +1859,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 b67b74559e260..c5a50cad41d89 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -17,11 +17,21 @@ 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.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; 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 +39,8 @@ 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; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM; @@ -62,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; @@ -111,6 +126,34 @@ 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; + // 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 * constraints from the first opaque activity beneath them. It's null for not @@ -193,6 +236,35 @@ final class LetterboxUiController { mExpandedTaskBarHeight = getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); + + mBooleanPropertyAllowOrientationOverride = + 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 = + isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT); + mIsOverrideToReverseLandscapeOrientationEnabled = + 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); } /** @@ -207,8 +279,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 { @@ -262,7 +334,7 @@ final class LetterboxUiController { if (!shouldEnableWithOverrideAndProperty( /* gatingCondition */ mLetterboxConfiguration ::isPolicyForIgnoringRequestedOrientationEnabled, - OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION, + mIsOverrideEnableCompatIgnoreRequestedOrientationEnabled, mBooleanPropertyIgnoreRequestedOrientation)) { return false; } @@ -307,6 +379,66 @@ 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 (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. @@ -322,7 +454,7 @@ final class LetterboxUiController { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), - OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH, + mIsOverrideCameraCompatDisableRefreshEnabled, mBooleanPropertyCameraCompatAllowRefresh); } @@ -344,7 +476,7 @@ final class LetterboxUiController { return shouldEnableWithOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), - OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE, + mIsOverrideCameraCompatEnableRefreshViaPauseEnabled, mBooleanPropertyCameraCompatEnableRefreshViaPause); } @@ -363,15 +495,19 @@ final class LetterboxUiController { return shouldEnableWithOptOutOverrideAndProperty( /* gatingCondition */ () -> mLetterboxConfiguration .isCameraCompatTreatmentEnabled(/* checkDeviceConfig */ true), - OVERRIDE_CAMERA_COMPAT_DISABLE_FORCE_ROTATION, + mIsOverrideCameraCompatDisableForceRotationEnabled, mBooleanPropertyCameraCompatAllowForceRotation); } + private boolean isCompatChangeEnabled(long overrideChangeId) { + return mActivityRecord.info.isChangeEnabled(overrideChangeId); + } + /** * 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} *
* @@ -379,12 +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) - && !mActivityRecord.info.isChangeEnabled(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; } /** @@ -393,21 +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 - * component {@code property} + * 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) - || mActivityRecord.info.isChangeEnabled(overrideChangeId); + return TRUE.equals(property) || isOverrideChangeEnabled; } boolean hasWallpaperBackgroundForLetterbox() { @@ -1224,7 +1377,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..c7f19fb1099d5 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,27 @@ 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.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; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -89,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; @@ -488,6 +502,130 @@ 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); + } + + // 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 */ ""); @@ -496,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(); @@ -505,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())) 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