diff options
8 files changed, 474 insertions, 25 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 18c29d196ada..bbbb79c79527 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b9259218c2d3..b0f6ae6085e3 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -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" /> diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 2c274827ba8e..e639866a6bab 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -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) { @@ -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) { @@ -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. @@ -7809,6 +7819,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable CompatDisplayInsets getCompatDisplayInsets() { + if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { + return mLetterboxUiController.getInheritedCompatDisplayInsets(); + } return mCompatDisplayInsets; } @@ -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; @@ -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(); } @@ -8061,6 +8081,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** + * @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. * * <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link @@ -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; } @@ -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(); @@ -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; } @@ -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(); @@ -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(); } @@ -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; } /** diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java index c19353cb2676..127a7bf1c9a5 100644 --- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java +++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java @@ -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; @@ -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; @@ -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, @@ -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(); } @@ -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) { @@ -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); + } } diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index bcea6f4db1dc..a53a5fc00b0c 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -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; @@ -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; @@ -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; @@ -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(); } @@ -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; } @@ -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; } @@ -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() { @@ -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() { @@ -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( @@ -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; + } + } } diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 80e7ddb432d3..5c893de6b920 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3938,27 +3938,54 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< unregisterConfigurationChangeListener(listener); } + static void overrideConfigurationPropagation(WindowContainer<?> receiver, + WindowContainer<?> supplier) { + overrideConfigurationPropagation(receiver, supplier, null /* configurationMerger */); + } + /** * Forces the receiver container to always use the configuration of the supplier container as * its requested override configuration. It allows to propagate configuration without changing * the relationship between child and parent. + * + * @param receiver The {@link WindowContainer<?>} which will receive the {@link + * Configuration} result of the merging operation. + * @param supplier The {@link WindowContainer<?>} which provides the initial {@link + * Configuration}. + * @param configurationMerger A {@link ConfigurationMerger} which combines the {@link + * Configuration} of the receiver and the supplier. */ - static void overrideConfigurationPropagation(WindowContainer<?> receiver, - WindowContainer<?> supplier) { + static WindowContainerListener overrideConfigurationPropagation(WindowContainer<?> receiver, + WindowContainer<?> supplier, @Nullable ConfigurationMerger configurationMerger) { final ConfigurationContainerListener listener = new ConfigurationContainerListener() { @Override public void onMergedOverrideConfigurationChanged(Configuration mergedOverrideConfig) { - receiver.onRequestedOverrideConfigurationChanged(supplier.getConfiguration()); + final Configuration mergedConfiguration = + configurationMerger != null + ? configurationMerger.merge(mergedOverrideConfig, + receiver.getConfiguration()) + : supplier.getConfiguration(); + receiver.onRequestedOverrideConfigurationChanged(mergedConfiguration); } }; supplier.registerConfigurationChangeListener(listener); - receiver.registerWindowContainerListener(new WindowContainerListener() { + final WindowContainerListener wcListener = new WindowContainerListener() { @Override public void onRemoved() { receiver.unregisterWindowContainerListener(this); supplier.unregisterConfigurationChangeListener(listener); } - }); + }; + receiver.registerWindowContainerListener(wcListener); + return wcListener; + } + + /** + * Abstraction for functions merging two {@link Configuration} objects into one. + */ + @FunctionalInterface + interface ConfigurationMerger { + Configuration merge(Configuration first, Configuration second); } /** diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index b8cf0ad2e774..4e692e2d212a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -963,6 +963,29 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runSetTranslucentLetterboxingEnabled(PrintWriter pw) { + String arg = getNextArg(); + final boolean enabled; + switch (arg) { + case "true": + case "1": + enabled = true; + break; + case "false": + case "0": + enabled = false; + break; + default: + getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg); + return -1; + } + + synchronized (mInternal.mGlobalLock) { + mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(enabled); + } + return 0; + } + private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException { if (peekNextArg() == null) { getErrPrintWriter().println("Error: No arguments provided."); @@ -1018,6 +1041,9 @@ public class WindowManagerShellCommand extends ShellCommand { case "--isSplitScreenAspectRatioForUnresizableAppsEnabled": runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw); break; + case "--isTranslucentLetterboxingEnabled": + runSetTranslucentLetterboxingEnabled(pw); + break; default: getErrPrintWriter().println( "Error: Unrecognized letterbox style option: " + arg); @@ -1081,6 +1107,9 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration .getIsSplitScreenAspectRatioForUnresizableAppsEnabled(); break; + case "isTranslucentLetterboxingEnabled": + mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); + break; default: getErrPrintWriter().println( "Error: Unrecognized letterbox style option: " + arg); @@ -1181,6 +1210,7 @@ public class WindowManagerShellCommand extends ShellCommand { mLetterboxConfiguration.resetDefaultPositionForVerticalReachability(); mLetterboxConfiguration.resetIsEducationEnabled(); mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled(); + mLetterboxConfiguration.resetTranslucentLetterboxingEnabled(); } } @@ -1217,7 +1247,6 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: " + mLetterboxConfiguration .getIsSplitScreenAspectRatioForUnresizableAppsEnabled()); - pw.println("Background type: " + LetterboxConfiguration.letterboxBackgroundTypeToString( mLetterboxConfiguration.getLetterboxBackgroundType())); @@ -1227,6 +1256,12 @@ public class WindowManagerShellCommand extends ShellCommand { + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius()); pw.println(" Wallpaper dark scrim alpha: " + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha()); + + if (mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { + pw.println("Letterboxing for translucent activities: enabled"); + } else { + pw.println("Letterboxing for translucent activities: disabled"); + } } return 0; } @@ -1419,12 +1454,16 @@ public class WindowManagerShellCommand extends ShellCommand { pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]"); pw.println(" Whether using split screen aspect ratio as a default aspect ratio for"); pw.println(" unresizable apps."); + pw.println(" --isTranslucentLetterboxingEnabled [true|1|false|0]"); + pw.println(" Whether letterboxing for translucent activities is enabled."); + pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType"); pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha"); pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier"); pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled"); - pw.println(" isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); - pw.println(" ||defaultPositionMultiplierForVerticalReachability]"); + pw.println(" |isEducationEnabled||defaultPositionMultiplierForHorizontalReachability"); + pw.println(" |isTranslucentLetterboxingEnabled"); + pw.println(" |defaultPositionMultiplierForVerticalReachability]"); pw.println(" Resets overrides to default values for specified properties separated"); pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'."); pw.println(" If no arguments provided, all values will be reset."); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index babad4d4d744..94c33f27f651 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -164,6 +164,114 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + public void testApplyStrategyToTranslucentActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + mActivity.info.setMinAspectRatio(1.2f); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(1.1f) + .setMaxAspectRatio(3f) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // We check bounds + final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); + final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); + assertEquals(opaqueBounds, translucentRequestedBounds); + // We check orientation + final int translucentOrientation = + translucentActivity.getRequestedConfigurationOrientation(); + assertEquals(ORIENTATION_PORTRAIT, translucentOrientation); + // We check aspect ratios + assertEquals(1.2f, translucentActivity.getMinAspectRatio(), 0.00001f); + assertEquals(1.5f, translucentActivity.getMaxAspectRatio(), 0.00001f); + } + + @Test + public void testNotApplyStrategyToTranslucentActivitiesWithDifferentUid() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + mActivity.info.setMinAspectRatio(1.2f); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(1.1f) + .setMaxAspectRatio(3f) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // We check bounds + final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); + final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); + assertNotEquals(opaqueBounds, translucentRequestedBounds); + } + + @Test + public void testApplyStrategyToMultipleTranslucentActivities() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2000, 1000); + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + mActivity.info.setMinAspectRatio(1.2f); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + // Translucent Activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .setMinAspectRatio(1.1f) + .setMaxAspectRatio(3f) + .build(); + doReturn(false).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // We check bounds + final Rect opaqueBounds = mActivity.getConfiguration().windowConfiguration.getBounds(); + final Rect translucentRequestedBounds = translucentActivity.getRequestedOverrideBounds(); + assertEquals(opaqueBounds, translucentRequestedBounds); + // Launch another translucent activity + final ActivityRecord translucentActivity2 = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) + .build(); + doReturn(false).when(translucentActivity2).fillsParent(); + mTask.addChild(translucentActivity2); + // We check bounds + final Rect translucent2RequestedBounds = translucentActivity2.getRequestedOverrideBounds(); + assertEquals(opaqueBounds, translucent2RequestedBounds); + } + + @Test + public void testTranslucentActivitiesDontGoInSizeCompactMode() { + mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); + setUpDisplaySizeWithApp(2800, 1400); + mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + prepareUnresizable(mActivity, -1f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); + // Rotate to put activity in size compat mode. + rotateDisplay(mActivity.mDisplayContent, ROTATION_90); + assertTrue(mActivity.inSizeCompatMode()); + // Rotate back + rotateDisplay(mActivity.mDisplayContent, ROTATION_0); + assertFalse(mActivity.inSizeCompatMode()); + // We launch a transparent activity + final ActivityRecord translucentActivity = new ActivityBuilder(mAtm) + .setLaunchedFromUid(mActivity.getUid()) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .build(); + doReturn(true).when(translucentActivity).fillsParent(); + mTask.addChild(translucentActivity); + // It should not be in SCM + assertFalse(translucentActivity.inSizeCompatMode()); + // We rotate again + rotateDisplay(translucentActivity.mDisplayContent, ROTATION_90); + assertFalse(translucentActivity.inSizeCompatMode()); + } + + @Test public void testRestartProcessIfVisible() { setUpDisplaySizeWithApp(1000, 2500); doNothing().when(mSupervisor).scheduleRestartTimeout(mActivity); |