Loading core/res/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -5300,6 +5300,10 @@ <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. --> <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool> <!-- Whether the specific behaviour for translucent activities letterboxing is enabled. TODO(b/255532890) Enable when ignoreOrientationRequest is set --> <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool> <!-- Whether a camera compat controller is enabled to allow the user to apply or revert treatment for stretched issues in camera viewfinder. --> <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool> Loading core/res/res/values/symbols.xml +3 −0 Original line number Diff line number Diff line Loading @@ -4401,6 +4401,9 @@ <!-- Set to true to make assistant show in front of the dream/screensaver. --> <java-symbol type="bool" name="config_assistantOnTopOfDream"/> <!-- Set to true to enable letterboxing on translucent activities. --> <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" /> <java-symbol type="string" name="config_overrideComponentUiPackage" /> <java-symbol type="string" name="notification_channel_network_status" /> Loading services/core/java/com/android/server/wm/ActivityRecord.java +56 −6 Original line number Diff line number Diff line Loading @@ -1239,8 +1239,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "supportsEnterPipOnTaskSwitch: " + supportsEnterPipOnTaskSwitch); } if (info.getMaxAspectRatio() != 0) { pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio()); if (getMaxAspectRatio() != 0) { pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio()); } final float minAspectRatio = getMinAspectRatio(); if (minAspectRatio != 0) { Loading Loading @@ -1590,6 +1590,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParent.setResumedActivity(this, "onParentChanged"); mImeInsetsFrozenUntilStartInput = false; } mLetterboxUiController.onActivityParentChanged(newParent); } if (rootTask != null && rootTask.topRunningActivity() == this) { Loading Loading @@ -7682,6 +7683,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Configuration.Orientation @Override int getRequestedConfigurationOrientation(boolean forDisplay) { if (mLetterboxUiController.hasInheritedOrientation()) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { return ActivityInfo.reverseOrientation( mLetterboxUiController.getInheritedOrientation()); } else { return mLetterboxUiController.getInheritedOrientation(); } } if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) { // We use Task here because we want to be consistent with what happens in // multi-window mode where other tasks orientations are ignored. Loading Loading @@ -7809,6 +7819,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedCompatDisplayInsets(); } return mCompatDisplayInsets; } Loading Loading @@ -7891,6 +7904,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets(); return; } if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; Loading Loading @@ -7952,6 +7969,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedSizeCompatScale(); } return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); } Loading Loading @@ -8060,6 +8080,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A logAppCompatState(); } /** * @return The orientation to use to understand if reachability is enabled. */ @ActivityInfo.ScreenOrientation int getOrientationForReachability() { return mLetterboxUiController.hasInheritedLetterboxBehavior() ? mLetterboxUiController.getInheritedOrientation() : getRequestedConfigurationOrientation(); } /** * Returns whether activity bounds are letterboxed. * Loading Loading @@ -8100,6 +8130,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!ignoreVisibility && !mVisibleRequested) { return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; } // TODO(b/256564921): Investigate if we need new metrics for translucent activities if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedAppCompatState(); } if (mInSizeCompatModeForBounds) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; } Loading Loading @@ -8570,6 +8604,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity // is letterboxed. return false; } final int appWidth = appBounds.width(); final int appHeight = appBounds.height(); final int containerAppWidth = containerBounds.width(); Loading @@ -8590,10 +8629,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The rest of the condition is that only one side is smaller than the container, but it // still needs to exclude the cases where the size is limited by the fixed aspect ratio. if (info.getMaxAspectRatio() > 0) { final float maxAspectRatio = getMaxAspectRatio(); if (maxAspectRatio > 0) { final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) / Math.min(appWidth, appHeight); if (aspectRatio >= info.getMaxAspectRatio()) { if (aspectRatio >= maxAspectRatio) { // The current size has reached the max aspect ratio. return false; } Loading Loading @@ -8815,7 +8855,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds, Rect containingBounds, float desiredAspectRatio) { final float maxAspectRatio = info.getMaxAspectRatio(); final float maxAspectRatio = getMaxAspectRatio(); final Task rootTask = getRootTask(); final float minAspectRatio = getMinAspectRatio(); final TaskFragment organizedTf = getOrganizedTaskFragment(); Loading Loading @@ -8922,6 +8962,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMinAspectRatio(); } if (info.applicationInfo == null) { return info.getMinAspectRatio(); } Loading Loading @@ -8966,11 +9009,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN; } float getMaxAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMaxAspectRatio(); } return info.getMaxAspectRatio(); } /** * Returns true if the activity has maximum or minimum aspect ratio. */ private boolean hasFixedAspectRatio() { return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; } /** Loading services/core/java/com/android/server/wm/LetterboxConfiguration.java +44 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Color; import android.provider.DeviceConfig; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -103,6 +104,10 @@ final class LetterboxConfiguration { final Context mContext; // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier @NonNull private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; // Aspect ratio of letterbox for fixed orientation, values <= // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. private float mFixedOrientationLetterboxAspectRatio; Loading Loading @@ -165,9 +170,12 @@ final class LetterboxConfiguration { // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled; // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier @NonNull private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; // Whether letterboxing strategy is enabled for translucent activities. If {@value false} // all the feature is disabled private boolean mTranslucentLetterboxingEnabled; // Allows to enable letterboxing strategy for translucent activities ignoring flags. private boolean mTranslucentLetterboxingOverrideEnabled; LetterboxConfiguration(Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext, Loading Loading @@ -206,6 +214,8 @@ final class LetterboxConfiguration { R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps)); mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsEnabledForTranslucentActivities); mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); } Loading Loading @@ -817,6 +827,32 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); } boolean isTranslucentLetterboxingEnabled() { return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled && isTranslucentLetterboxingAllowed()); } void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) { mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled; } void setTranslucentLetterboxingOverrideEnabled( boolean translucentLetterboxingOverrideEnabled) { mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled; setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled); } /** * Resets whether we use the constraints override strategy for letterboxing when dealing * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}. */ void resetTranslucentLetterboxingEnabled() { final boolean newValue = mContext.getResources().getBoolean( R.bool.config_letterboxIsEnabledForTranslucentActivities); setTranslucentLetterboxingEnabled(newValue); setTranslucentLetterboxingOverrideEnabled(false); } /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */ private void updatePositionForHorizontalReachability( Function<Integer, Integer> newHorizonalPositionFun) { Loading @@ -839,4 +875,9 @@ final class LetterboxConfiguration { nextVerticalPosition); } // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener static boolean isTranslucentLetterboxingAllowed() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, "enable_translucent_activity_letterbox", false); } } services/core/java/com/android/server/wm/LetterboxUiController.java +185 −8 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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.LayoutParams.FLAG_SHOW_WALLPAPER; Loading @@ -27,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT; Loading Loading @@ -82,13 +84,44 @@ final class LetterboxUiController { private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; private static final float UNDEFINED_ASPECT_RATIO = 0f; private final Point mTmpPoint = new Point(); private final LetterboxConfiguration mLetterboxConfiguration; private final ActivityRecord mActivityRecord; /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not * translucent activities. */ @Nullable private WindowContainerListener mLetterboxConfigListener; private boolean mShowWallpaperForLetterboxBackground; // In case of transparent activities we might need to access the aspectRatio of the // first opaque activity beneath. private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; @Configuration.Orientation private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. private boolean mIsInheritedInSizeCompatMode; // This is the SizeCompatScale of the opaque activity beneath a translucent one private float mInheritedSizeCompatScale; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @Nullable private Letterbox mLetterbox; Loading Loading @@ -220,7 +253,9 @@ final class LetterboxUiController { : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTask().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint); final Rect innerFrame = hasInheritedLetterboxBehavior() ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); } Loading Loading @@ -305,7 +340,9 @@ final class LetterboxUiController { } private void handleHorizontalDoubleTap(int x) { if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { // TODO(b/260857308): Investigate if enabling reachability for translucent activity if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { return; } Loading Loading @@ -341,7 +378,9 @@ final class LetterboxUiController { } private void handleVerticalDoubleTap(int y) { if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { // TODO(b/260857308): Investigate if enabling reachability for translucent activity if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { return; } Loading Loading @@ -390,7 +429,7 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT); && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT); } private boolean isHorizontalReachabilityEnabled() { Loading @@ -412,7 +451,7 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_PORTRAIT && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE); && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE); } private boolean isVerticalReachabilityEnabled() { Loading Loading @@ -576,9 +615,7 @@ final class LetterboxUiController { // Rounded corners should be displayed above the taskbar. bounds.bottom = Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top); if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) { bounds.scale(1.0f / mActivityRecord.getCompatScale()); } scaleIfNeeded(bounds); } private int getInsetsStateCornerRadius( Loading Loading @@ -788,4 +825,144 @@ final class LetterboxUiController { w.mAttrs.insetsFlags.appearance ); } /** * Handles translucent activities letterboxing inheriting constraints from the * first opaque activity beneath. * @param parent The parent container. */ void onActivityParentChanged(WindowContainer<?> parent) { if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { return; } if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null || mActivityRecord.fillsParent()) { return; } final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */, true /* traverseTopToBottom */); if (firstOpaqueActivityBeneath == null || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) { // We skip letterboxing if the translucent activity doesn't have any opaque // activities beneath of if it's launched from a different user (e.g. notification) return; } inheritConfiguration(firstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, firstOpaqueActivityBeneath, (opaqueConfig, transparentConfig) -> { final Configuration mutatedConfiguration = new Configuration(); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. bounds.set(parentBounds.left, parentBounds.top, parentBounds.left + letterboxBounds.width(), parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); return mutatedConfiguration; }); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath. In this case it will inherit bounds, orientation and aspect ratios from * the first opaque activity beneath. */ boolean hasInheritedLetterboxBehavior() { return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds(); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath and needs to inherit its orientation. */ boolean hasInheritedOrientation() { // To force a different orientation, the transparent one needs to have an explicit one // otherwise the existing one is fine and the actual orientation will depend on the // bounds. // 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; } float getInheritedMinAspectRatio() { return mInheritedMinAspectRatio; } float getInheritedMaxAspectRatio() { return mInheritedMaxAspectRatio; } int getInheritedAppCompatState() { return mInheritedAppCompatState; } float getInheritedSizeCompatScale() { return mInheritedSizeCompatScale; } @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; } public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } private void inheritConfiguration(ActivityRecord firstOpaque) { // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also // not resizable. if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); } if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); mInheritedSizeCompatScale = firstOpaque.getCompatScale(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); } private void clearInheritedConfig() { mLetterboxConfigListener = null; mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mIsInheritedInSizeCompatMode = false; mInheritedSizeCompatScale = 1f; mInheritedCompatDisplayInsets = null; } private void scaleIfNeeded(Rect bounds) { if (boundsNeedToScale()) { bounds.scale(1.0f / mActivityRecord.getCompatScale()); } } private boolean boundsNeedToScale() { if (hasInheritedLetterboxBehavior()) { return mIsInheritedInSizeCompatMode && mInheritedSizeCompatScale < 1.0f; } else { return mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f; } } } Loading
core/res/res/values/config.xml +4 −0 Original line number Diff line number Diff line Loading @@ -5300,6 +5300,10 @@ <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. --> <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool> <!-- Whether the specific behaviour for translucent activities letterboxing is enabled. TODO(b/255532890) Enable when ignoreOrientationRequest is set --> <bool name="config_letterboxIsEnabledForTranslucentActivities">false</bool> <!-- Whether a camera compat controller is enabled to allow the user to apply or revert treatment for stretched issues in camera viewfinder. --> <bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool> Loading
core/res/res/values/symbols.xml +3 −0 Original line number Diff line number Diff line Loading @@ -4401,6 +4401,9 @@ <!-- Set to true to make assistant show in front of the dream/screensaver. --> <java-symbol type="bool" name="config_assistantOnTopOfDream"/> <!-- Set to true to enable letterboxing on translucent activities. --> <java-symbol type="bool" name="config_letterboxIsEnabledForTranslucentActivities" /> <java-symbol type="string" name="config_overrideComponentUiPackage" /> <java-symbol type="string" name="notification_channel_network_status" /> Loading
services/core/java/com/android/server/wm/ActivityRecord.java +56 −6 Original line number Diff line number Diff line Loading @@ -1239,8 +1239,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A pw.println(prefix + "supportsEnterPipOnTaskSwitch: " + supportsEnterPipOnTaskSwitch); } if (info.getMaxAspectRatio() != 0) { pw.println(prefix + "maxAspectRatio=" + info.getMaxAspectRatio()); if (getMaxAspectRatio() != 0) { pw.println(prefix + "maxAspectRatio=" + getMaxAspectRatio()); } final float minAspectRatio = getMinAspectRatio(); if (minAspectRatio != 0) { Loading Loading @@ -1590,6 +1590,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParent.setResumedActivity(this, "onParentChanged"); mImeInsetsFrozenUntilStartInput = false; } mLetterboxUiController.onActivityParentChanged(newParent); } if (rootTask != null && rootTask.topRunningActivity() == this) { Loading Loading @@ -7682,6 +7683,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Configuration.Orientation @Override int getRequestedConfigurationOrientation(boolean forDisplay) { if (mLetterboxUiController.hasInheritedOrientation()) { final RootDisplayArea root = getRootDisplayArea(); if (forDisplay && root != null && root.isOrientationDifferentFromDisplay()) { return ActivityInfo.reverseOrientation( mLetterboxUiController.getInheritedOrientation()); } else { return mLetterboxUiController.getInheritedOrientation(); } } if (mOrientation == SCREEN_ORIENTATION_BEHIND && task != null) { // We use Task here because we want to be consistent with what happens in // multi-window mode where other tasks orientations are ignored. Loading Loading @@ -7809,6 +7819,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedCompatDisplayInsets(); } return mCompatDisplayInsets; } Loading Loading @@ -7891,6 +7904,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets(); return; } if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; Loading Loading @@ -7952,6 +7969,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedSizeCompatScale(); } return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); } Loading Loading @@ -8060,6 +8080,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A logAppCompatState(); } /** * @return The orientation to use to understand if reachability is enabled. */ @ActivityInfo.ScreenOrientation int getOrientationForReachability() { return mLetterboxUiController.hasInheritedLetterboxBehavior() ? mLetterboxUiController.getInheritedOrientation() : getRequestedConfigurationOrientation(); } /** * Returns whether activity bounds are letterboxed. * Loading Loading @@ -8100,6 +8130,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!ignoreVisibility && !mVisibleRequested) { return APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE; } // TODO(b/256564921): Investigate if we need new metrics for translucent activities if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedAppCompatState(); } if (mInSizeCompatModeForBounds) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; } Loading Loading @@ -8570,6 +8604,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity // is letterboxed. return false; } final int appWidth = appBounds.width(); final int appHeight = appBounds.height(); final int containerAppWidth = containerBounds.width(); Loading @@ -8590,10 +8629,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The rest of the condition is that only one side is smaller than the container, but it // still needs to exclude the cases where the size is limited by the fixed aspect ratio. if (info.getMaxAspectRatio() > 0) { final float maxAspectRatio = getMaxAspectRatio(); if (maxAspectRatio > 0) { final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) / Math.min(appWidth, appHeight); if (aspectRatio >= info.getMaxAspectRatio()) { if (aspectRatio >= maxAspectRatio) { // The current size has reached the max aspect ratio. return false; } Loading Loading @@ -8815,7 +8855,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds, Rect containingBounds, float desiredAspectRatio) { final float maxAspectRatio = info.getMaxAspectRatio(); final float maxAspectRatio = getMaxAspectRatio(); final Task rootTask = getRootTask(); final float minAspectRatio = getMinAspectRatio(); final TaskFragment organizedTf = getOrganizedTaskFragment(); Loading Loading @@ -8922,6 +8962,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * Returns the min aspect ratio of this activity. */ float getMinAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMinAspectRatio(); } if (info.applicationInfo == null) { return info.getMinAspectRatio(); } Loading Loading @@ -8966,11 +9009,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && parent.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN; } float getMaxAspectRatio() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedMaxAspectRatio(); } return info.getMaxAspectRatio(); } /** * Returns true if the activity has maximum or minimum aspect ratio. */ private boolean hasFixedAspectRatio() { return info.getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; return getMaxAspectRatio() != 0 || getMinAspectRatio() != 0; } /** Loading
services/core/java/com/android/server/wm/LetterboxConfiguration.java +44 −3 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.graphics.Color; import android.provider.DeviceConfig; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -103,6 +104,10 @@ final class LetterboxConfiguration { final Context mContext; // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier @NonNull private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; // Aspect ratio of letterbox for fixed orientation, values <= // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored. private float mFixedOrientationLetterboxAspectRatio; Loading Loading @@ -165,9 +170,12 @@ final class LetterboxConfiguration { // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled; // Responsible for the persistence of letterbox[Horizontal|Vertical]PositionMultiplier @NonNull private final LetterboxConfigurationPersister mLetterboxConfigurationPersister; // Whether letterboxing strategy is enabled for translucent activities. If {@value false} // all the feature is disabled private boolean mTranslucentLetterboxingEnabled; // Allows to enable letterboxing strategy for translucent activities ignoring flags. private boolean mTranslucentLetterboxingOverrideEnabled; LetterboxConfiguration(Context systemUiContext) { this(systemUiContext, new LetterboxConfigurationPersister(systemUiContext, Loading Loading @@ -206,6 +214,8 @@ final class LetterboxConfiguration { R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps)); mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); mTranslucentLetterboxingEnabled = mContext.getResources().getBoolean( R.bool.config_letterboxIsEnabledForTranslucentActivities); mLetterboxConfigurationPersister = letterboxConfigurationPersister; mLetterboxConfigurationPersister.start(); } Loading Loading @@ -817,6 +827,32 @@ final class LetterboxConfiguration { R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled); } boolean isTranslucentLetterboxingEnabled() { return mTranslucentLetterboxingOverrideEnabled || (mTranslucentLetterboxingEnabled && isTranslucentLetterboxingAllowed()); } void setTranslucentLetterboxingEnabled(boolean translucentLetterboxingEnabled) { mTranslucentLetterboxingEnabled = translucentLetterboxingEnabled; } void setTranslucentLetterboxingOverrideEnabled( boolean translucentLetterboxingOverrideEnabled) { mTranslucentLetterboxingOverrideEnabled = translucentLetterboxingOverrideEnabled; setTranslucentLetterboxingEnabled(translucentLetterboxingOverrideEnabled); } /** * Resets whether we use the constraints override strategy for letterboxing when dealing * with translucent activities {@link R.bool.config_letterboxIsEnabledForTranslucentActivities}. */ void resetTranslucentLetterboxingEnabled() { final boolean newValue = mContext.getResources().getBoolean( R.bool.config_letterboxIsEnabledForTranslucentActivities); setTranslucentLetterboxingEnabled(newValue); setTranslucentLetterboxingOverrideEnabled(false); } /** Calculates a new letterboxPositionForHorizontalReachability value and updates the store */ private void updatePositionForHorizontalReachability( Function<Integer, Integer> newHorizonalPositionFun) { Loading @@ -839,4 +875,9 @@ final class LetterboxConfiguration { nextVerticalPosition); } // TODO(b/262378106): Cache runtime flag and implement DeviceConfig.OnPropertiesChangedListener static boolean isTranslucentLetterboxingAllowed() { return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_WINDOW_MANAGER, "enable_translucent_activity_letterbox", false); } }
services/core/java/com/android/server/wm/LetterboxUiController.java +185 −8 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server.wm; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 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.LayoutParams.FLAG_SHOW_WALLPAPER; Loading @@ -27,6 +28,7 @@ import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANG import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION; import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM; import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT; Loading Loading @@ -82,13 +84,44 @@ final class LetterboxUiController { private static final String TAG = TAG_WITH_CLASS_NAME ? "LetterboxUiController" : TAG_ATM; private static final float UNDEFINED_ASPECT_RATIO = 0f; private final Point mTmpPoint = new Point(); private final LetterboxConfiguration mLetterboxConfiguration; private final ActivityRecord mActivityRecord; /* * WindowContainerListener responsible to make translucent activities inherit * constraints from the first opaque activity beneath them. It's null for not * translucent activities. */ @Nullable private WindowContainerListener mLetterboxConfigListener; private boolean mShowWallpaperForLetterboxBackground; // In case of transparent activities we might need to access the aspectRatio of the // first opaque activity beneath. private float mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; private float mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; @Configuration.Orientation private int mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. private boolean mIsInheritedInSizeCompatMode; // This is the SizeCompatScale of the opaque activity beneath a translucent one private float mInheritedSizeCompatScale; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; @Nullable private Letterbox mLetterbox; Loading Loading @@ -220,7 +253,9 @@ final class LetterboxUiController { : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTask().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint); final Rect innerFrame = hasInheritedLetterboxBehavior() ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); } Loading Loading @@ -305,7 +340,9 @@ final class LetterboxUiController { } private void handleHorizontalDoubleTap(int x) { if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { // TODO(b/260857308): Investigate if enabling reachability for translucent activity if (hasInheritedLetterboxBehavior() || !isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) { return; } Loading Loading @@ -341,7 +378,9 @@ final class LetterboxUiController { } private void handleVerticalDoubleTap(int y) { if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { // TODO(b/260857308): Investigate if enabling reachability for translucent activity if (hasInheritedLetterboxBehavior() || !isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) { return; } Loading Loading @@ -390,7 +429,7 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT); && mActivityRecord.getOrientationForReachability() == ORIENTATION_PORTRAIT); } private boolean isHorizontalReachabilityEnabled() { Loading @@ -412,7 +451,7 @@ final class LetterboxUiController { && parentConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN && (parentConfiguration.orientation == ORIENTATION_PORTRAIT && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE); && mActivityRecord.getOrientationForReachability() == ORIENTATION_LANDSCAPE); } private boolean isVerticalReachabilityEnabled() { Loading Loading @@ -576,9 +615,7 @@ final class LetterboxUiController { // Rounded corners should be displayed above the taskbar. bounds.bottom = Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top); if (mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f) { bounds.scale(1.0f / mActivityRecord.getCompatScale()); } scaleIfNeeded(bounds); } private int getInsetsStateCornerRadius( Loading Loading @@ -788,4 +825,144 @@ final class LetterboxUiController { w.mAttrs.insetsFlags.appearance ); } /** * Handles translucent activities letterboxing inheriting constraints from the * first opaque activity beneath. * @param parent The parent container. */ void onActivityParentChanged(WindowContainer<?> parent) { if (!mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { return; } if (mLetterboxConfigListener != null) { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null || mActivityRecord.fillsParent()) { return; } final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( ActivityRecord::fillsParent, mActivityRecord, false /* includeBoundary */, true /* traverseTopToBottom */); if (firstOpaqueActivityBeneath == null || mActivityRecord.launchedFromUid != firstOpaqueActivityBeneath.getUid()) { // We skip letterboxing if the translucent activity doesn't have any opaque // activities beneath of if it's launched from a different user (e.g. notification) return; } inheritConfiguration(firstOpaqueActivityBeneath); mLetterboxConfigListener = WindowContainer.overrideConfigurationPropagation( mActivityRecord, firstOpaqueActivityBeneath, (opaqueConfig, transparentConfig) -> { final Configuration mutatedConfiguration = new Configuration(); final Rect parentBounds = parent.getWindowConfiguration().getBounds(); final Rect bounds = mutatedConfiguration.windowConfiguration.getBounds(); final Rect letterboxBounds = opaqueConfig.windowConfiguration.getBounds(); // We cannot use letterboxBounds directly here because the position relies on // letterboxing. Using letterboxBounds directly, would produce a double offset. bounds.set(parentBounds.left, parentBounds.top, parentBounds.left + letterboxBounds.width(), parentBounds.top + letterboxBounds.height()); // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); return mutatedConfiguration; }); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath. In this case it will inherit bounds, orientation and aspect ratios from * the first opaque activity beneath. */ boolean hasInheritedLetterboxBehavior() { return mLetterboxConfigListener != null && !mActivityRecord.matchParentBounds(); } /** * @return {@code true} if the current activity is translucent with an opaque activity * beneath and needs to inherit its orientation. */ boolean hasInheritedOrientation() { // To force a different orientation, the transparent one needs to have an explicit one // otherwise the existing one is fine and the actual orientation will depend on the // bounds. // 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; } float getInheritedMinAspectRatio() { return mInheritedMinAspectRatio; } float getInheritedMaxAspectRatio() { return mInheritedMaxAspectRatio; } int getInheritedAppCompatState() { return mInheritedAppCompatState; } float getInheritedSizeCompatScale() { return mInheritedSizeCompatScale; } @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; } public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } private void inheritConfiguration(ActivityRecord firstOpaque) { // To avoid wrong behaviour, we're not forcing a specific aspet ratio to activities // which are not already providing one (e.g. permission dialogs) and presumably also // not resizable. if (mActivityRecord.getMinAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMinAspectRatio = firstOpaque.getMinAspectRatio(); } if (mActivityRecord.getMaxAspectRatio() != UNDEFINED_ASPECT_RATIO) { mInheritedMaxAspectRatio = firstOpaque.getMaxAspectRatio(); } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); mInheritedSizeCompatScale = firstOpaque.getCompatScale(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); } private void clearInheritedConfig() { mLetterboxConfigListener = null; mInheritedMinAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mIsInheritedInSizeCompatMode = false; mInheritedSizeCompatScale = 1f; mInheritedCompatDisplayInsets = null; } private void scaleIfNeeded(Rect bounds) { if (boundsNeedToScale()) { bounds.scale(1.0f / mActivityRecord.getCompatScale()); } } private boolean boundsNeedToScale() { if (hasInheritedLetterboxBehavior()) { return mIsInheritedInSizeCompatMode && mInheritedSizeCompatScale < 1.0f; } else { return mActivityRecord.inSizeCompatMode() && mActivityRecord.getCompatScale() < 1.0f; } } }