Fix Letterboxing for Transparent Activities
We want a not resizable transparent activity to inherits the constraints from the activity below. This allows the transparent activity to be letterboxed. The specific logic for transparent activities is enabled when the config_letterboxIsEnabledForTranslucentActivities flag is true. Fixes: 220703089 Test: Run `atest SizeCompatTests` with the following tests: testApplyStrategyToTranslucentActivities testTranslucentActivitiesDontGoInSizeCompactMode testNotApplyStrategyToTranslucentActivitiesWithDifferentUid testApplyStrategyToMultipleTranslucentActivities Change-Id: I6b5e8517ae8286f7543c3f57c158276aab6cfbfc
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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) {
|
||||
@@ -7676,6 +7677,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.
|
||||
@@ -7803,6 +7813,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
|
||||
@Nullable
|
||||
CompatDisplayInsets getCompatDisplayInsets() {
|
||||
if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
|
||||
return mLetterboxUiController.getInheritedCompatDisplayInsets();
|
||||
}
|
||||
return mCompatDisplayInsets;
|
||||
}
|
||||
|
||||
@@ -7885,6 +7898,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;
|
||||
@@ -7946,6 +7963,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
|
||||
|
||||
@Override
|
||||
float getCompatScale() {
|
||||
if (mLetterboxUiController.hasInheritedLetterboxBehavior()) {
|
||||
return mLetterboxUiController.getInheritedSizeCompatScale();
|
||||
}
|
||||
return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale();
|
||||
}
|
||||
|
||||
@@ -8054,6 +8074,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.
|
||||
*
|
||||
@@ -8094,6 +8124,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;
|
||||
}
|
||||
@@ -8564,6 +8598,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();
|
||||
@@ -8584,10 +8623,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;
|
||||
}
|
||||
@@ -8809,7 +8849,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();
|
||||
@@ -8916,6 +8956,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();
|
||||
}
|
||||
@@ -8960,11 +9003,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3946,27 +3946,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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.");
|
||||
|
||||
@@ -163,6 +163,114 @@ public class SizeCompatTests extends WindowTestsBase {
|
||||
setUpApp(builder.build());
|
||||
}
|
||||
|
||||
@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);
|
||||
|
||||
Reference in New Issue
Block a user