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
This treatment is enabled when the following conditions are met: + *
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 { *
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