From 31d25e9ebc6f81a556b801003e42226f6a3e72a4 Mon Sep 17 00:00:00 2001 From: Mariia Sandrikova Date: Wed, 10 Feb 2021 21:13:00 +0000 Subject: [PATCH] Letterbox positioning (1/4): Consolidate activity and task letterboxing Now letterboxing can happen on both task (fixed orientation) and activity (size compat or aspect ratio restrictions) levels. This change consolidates them to prevent future changes for letterbox positioning from being split between Task and ActivityRecord. Main changes: - Move Task#computeLetterboxBounds to ActivityRecord#resolveFixedOrientationConfiguration that is called from ActivityRecord#resolveOverrideConfiguration. - Since fixed orientation letterboxing is no longer included in parent (task) bounds of the activity, account for fixed orientation letterbox bounds in size compat methods that relies on that. - Replace all calls of ActivityRecord#updateCompatDisplayInsets (renamed updateSizeCompatMode) with just one call in ActivityRecord#resolveOverrideConfiguration. - Refactor ActivityRecord#inSizeCompatMode to use mInSizeCompatModeForBounds pre-calculated in ActivityRecord#resolveSizeCompatModeConfiguration. Changes planned in next CLs: (2/4) Consolidate positioning logic in ActivityRecord#positionLetterboxedBounds(...) called from ActivityRecord#resolveOverrideConfiguration after bounds dimensions are computed. (3/4) Position bounds using gravity specified in config or via ADB commands. (4/4) Rename WindowManagerService#getTaskLetterboxAspectRatio and all related methods since fixed orientation letterboxing now happens on activity level. Test: atest WmTests Bug: 175212232 Change-Id: I8b1f7ac178bef2105c46940ddd3a493c547f0be9 --- .../com/android/server/wm/ActivityRecord.java | 380 ++++++++++++------ .../core/java/com/android/server/wm/Task.java | 129 +----- .../com/android/server/wm/WindowState.java | 22 +- .../server/wm/ActivityRecordTests.java | 5 +- .../wm/DualDisplayAreaGroupPolicyTest.java | 34 +- .../android/server/wm/SizeCompatTests.java | 123 +++--- .../android/server/wm/TaskRecordTests.java | 27 +- .../src/com/android/server/wm/TaskTests.java | 20 - 8 files changed, 373 insertions(+), 367 deletions(-) diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index f440e566d47e1..bf91f98ba90ef 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -40,8 +40,11 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.activityTypeToString; +import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.app.servertransaction.TransferSplashScreenViewStateItem.ATTACH_TO; import static android.app.servertransaction.TransferSplashScreenViewStateItem.HANDOVER_TO; import static android.content.Intent.ACTION_MAIN; @@ -209,6 +212,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING; import static com.android.server.wm.WindowManagerService.LETTERBOX_BACKGROUND_SOLID_COLOR; +import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL; import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES; import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY; @@ -556,7 +560,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * The precomputed display insets for resolving configuration. It will be non-null if - * {@link #shouldUseSizeCompatMode} returns {@code true}. + * {@link #shouldCreateCompatDisplayInsets} returns {@code true}. */ private CompatDisplayInsets mCompatDisplayInsets; @@ -646,6 +650,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A */ private Rect mSizeCompatBounds; + // Whether this activity is in size compatibility mode because its bounds don't fit in parent + // naturally. + private boolean mInSizeCompatModeForBounds = false; + + // Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed + // orientation then aspect ratio restrictions are also already respected. + // This happens when an activity has fixed orientation which doesn't match orientation of the + // parent because a display is ignoring orientation request or fixed to user rotation. + // See WindowManagerService#getIgnoreOrientationRequest and + // WindowManagerService#getFixedToUserRotation for more context. + private boolean mIsLetterboxedForFixedOrientationAndAspectRatio = false; + // activity is not displayed? // TODO: rename to mNoDisplay @VisibleForTesting @@ -1294,9 +1310,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Maybe this call should be moved inside // updateOverrideConfiguration() newTask.updateOverrideConfigurationFromLaunchBounds(); - // Make sure override configuration is up-to-date before using to create window - // controller. - updateSizeCompatMode(); // When an activity is started directly into a split-screen fullscreen root task, we // need to update the initial multi-window modes so that the callbacks are scheduled // correctly when the user leaves that mode. @@ -6701,12 +6714,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } if (onDescendantOrientationChanged(this)) { - // The app is just becoming visible, and the parent Task has updated with the - // orientation request. Update the size compat mode. - updateSizeCompatMode(); - // WM Shell can override WM Core positioning (e.g. for letterboxing) so ensure - // that WM Shell is called when an activity becomes visible. Without this, WM Core - // will handle positioning instead of WM Shell when an app is reopened. + // WM Shell can show additional UI elements, e.g. a restart button for size compat mode + // so ensure that WM Shell is called when an activity becomes visible. task.dispatchTaskInfoChangedIfNeeded(/* force= */ true); } } @@ -6771,7 +6780,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * density than its parent or its bounds don't fit in parent naturally. */ boolean inSizeCompatMode() { - if (mCompatDisplayInsets == null || !shouldUseSizeCompatMode() + if (mInSizeCompatModeForBounds) { + return true; + } + if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets() // The orientation is different from parent when transforming. || isFixedRotationTransforming()) { return false; @@ -6781,70 +6793,30 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The app bounds hasn't been computed yet. return false; } - final Configuration parentConfig = getParent().getConfiguration(); // Although colorMode, screenLayout, smallestScreenWidthDp are also fixed, generally these // fields should be changed with density and bounds, so here only compares the most // significant field. - if (parentConfig.densityDpi != getConfiguration().densityDpi) { - return true; - } - - final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); - final int appWidth = appBounds.width(); - final int appHeight = appBounds.height(); - final int parentAppWidth = parentAppBounds.width(); - final int parentAppHeight = parentAppBounds.height(); - if (parentAppWidth == appWidth && parentAppHeight == appHeight) { - // Matched the parent bounds. - return false; - } - if (parentAppWidth > appWidth && parentAppHeight > appHeight) { - // Both sides are smaller than the parent. - return true; - } - if (parentAppWidth < appWidth || parentAppHeight < appHeight) { - // One side is larger than the parent. - return true; - } - - // The rest of the condition is that only one side is smaller than the parent, but it still - // needs to exclude the cases where the size is limited by the fixed aspect ratio. - if (info.maxAspectRatio > 0) { - final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) - / Math.min(appWidth, appHeight); - if (aspectRatio >= info.maxAspectRatio) { - // The current size has reached the max aspect ratio. - return false; - } - } - if (info.minAspectRatio > 0) { - // The activity should have at least the min aspect ratio, so this checks if the parent - // still has available space to provide larger aspect ratio. - final float parentAspectRatio = (0.5f + Math.max(parentAppWidth, parentAppHeight)) - / Math.min(parentAppWidth, parentAppHeight); - if (parentAspectRatio <= info.minAspectRatio) { - // The long side has reached the parent. - return false; - } - } - return true; + return parentConfig.densityDpi != getConfiguration().densityDpi; } /** * Indicates the activity will keep the bounds and screen configuration when it was first * launched, no matter how its parent changes. * + *

If {@true}, then {@link CompatDisplayInsets} will be created in {@link + * #resolveOverrideConfiguration} to "freeze" activity bounds and insets. + * * @return {@code true} if this activity is declared as non-resizable and fixed orientation or * aspect ratio. */ - boolean shouldUseSizeCompatMode() { + boolean shouldCreateCompatDisplayInsets() { if (info.supportsSizeChanges() != ActivityInfo.SIZE_CHANGES_UNSUPPORTED) { return false; } if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) { final ActivityRecord root = task != null ? task.getRootActivity() : null; - if (root != null && root != this && !root.shouldUseSizeCompatMode()) { + if (root != null && root != this && !root.shouldCreateCompatDisplayInsets()) { // If the root activity doesn't use size compatibility mode, the activities above // are forced to be the same for consistent visual appearance. return false; @@ -6866,25 +6838,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. - private void updateSizeCompatMode() { - if (mCompatDisplayInsets != null || !shouldUseSizeCompatMode()) { + private void updateCompatDisplayInsets(@Nullable Rect fixedOrientationBounds) { + if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; } - final Configuration parentConfig = getParent().getConfiguration(); - if (!hasProcess() && !isConfigurationCompatible(parentConfig)) { - // Don't compute when launching in fullscreen and the fixed orientation is not the - // current orientation. It is more accurately to compute the override bounds from - // the updated configuration after the fixed orientation is applied. - return; - } - - if (task == null || (!handlesOrientationChangeFromDescendant() - && task.getLastTaskBoundsComputeActivity() != this)) { - // Don't compute when Task hasn't computed its bounds for this app, because the Task can - // be letterboxed, and its bounds may not be accurate until then. - return; - } Configuration overrideConfig = getRequestedOverrideConfiguration(); final Configuration fullConfig = getConfiguration(); @@ -6907,17 +6865,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } // The role of CompatDisplayInsets is like the override bounds. - mCompatDisplayInsets = new CompatDisplayInsets(mDisplayContent, this); + mCompatDisplayInsets = + new CompatDisplayInsets(mDisplayContent, this, fixedOrientationBounds); } @VisibleForTesting void clearSizeCompatMode() { + mInSizeCompatModeForBounds = false; mSizeCompatScale = 1f; mSizeCompatBounds = null; mCompatDisplayInsets = null; - // Recompute from Task because letterbox can also happen on Task level. - task.onRequestedOverrideConfigurationChanged(task.getRequestedOverrideConfiguration()); + onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); } @Override @@ -6952,23 +6911,41 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTmpConfig.updateFrom(resolvedConfig); newParentConfiguration = mTmpConfig; } + + final int windowingMode = getWindowingMode(); + // TODO(b/181207944): Consider removing the if condition and always run + // resolveFixedOrientationConfiguration() since this should be applied for all cases. + if (isSplitScreenWindowingMode(windowingMode) + || windowingMode == WINDOWING_MODE_MULTI_WINDOW + || windowingMode == WINDOWING_MODE_FULLSCREEN) { + resolveFixedOrientationConfiguration(newParentConfiguration); + } + final Rect fixedOrientationBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(resolvedConfig.windowConfiguration.getBounds()) : null; + if (mCompatDisplayInsets != null) { resolveSizeCompatModeConfiguration(newParentConfiguration); - } else { - if (inMultiWindowMode()) { - // We ignore activities' requested orientation in multi-window modes. Task level may - // take them into consideration when calculating bounds. - resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED; - // If the activity has requested override bounds, the configuration needs to be - // computed accordingly. - if (!matchParentBounds()) { - task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); - } - } else { - resolveFullscreenConfiguration(newParentConfiguration); + } else if (inMultiWindowMode()) { + // We ignore activities' requested orientation in multi-window modes. They may be + // taken into consideration in resolveFixedOrientationConfiguration call above. + resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED; + // If the activity has requested override bounds, the configuration needs to be + // computed accordingly. + if (!matchParentBounds()) { + task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration); } + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration. + } else if (!isLetterboxedForFixedOrientationAndAspectRatio()) { + resolveFullscreenConfiguration(newParentConfiguration); } + if (mVisibleRequested) { + updateCompatDisplayInsets(fixedOrientationBounds); + } + + // TODO(b/175212232): Consolidate position logic from each "resolve" method above here. + // Assign configuration sequence number into hierarchy because there is a different way than // ensureActivityConfiguration() in this class that uses configuration in WindowState during // layout traversals. @@ -6976,6 +6953,109 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A getResolvedOverrideConfiguration().seq = mConfigurationSeq; } + /** + * Whether this activity is letterboxed for fixed orientation. If letterboxed due to fixed + * orientation then aspect ratio restrictions are also already respected. + * + *

This happens when an activity has fixed orientation which doesn't match orientation of the + * parent because a display setting 'ignoreOrientationRequest' is set to true. See {@link + * WindowManagerService#getIgnoreOrientationRequest} for more context. + */ + boolean isLetterboxedForFixedOrientationAndAspectRatio() { + return mIsLetterboxedForFixedOrientationAndAspectRatio; + } + + /** + * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation + * change and the requested orientation is different from the parent. + * + *

If letterboxed due to fixed orientation then aspect ratio restrictions are also applied + * in this methiod. + */ + private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) { + mIsLetterboxedForFixedOrientationAndAspectRatio = false; + if (handlesOrientationChangeFromDescendant()) { + // No need to letterbox because of fixed orientation. Display will handle + // fixed-orientation requests. + return; + } + + final Rect resolvedBounds = + getResolvedOverrideConfiguration().windowConfiguration.getBounds(); + final int parentOrientation = newParentConfig.orientation; + + // If the activity requires a different orientation (either by override or activityInfo), + // make it fit the available bounds by scaling down its bounds. + final int forcedOrientation = getRequestedConfigurationOrientation(); + if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { + return; + } + + if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) { + // App prefers to keep its original size. + // If the size compat is from previous fixed orientation letterboxing, we may want to + // have fixed orientation letterbox again, otherwise it will show the size compat + // restart button even if the restart bounds will be the same. + return; + } + + final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); + final int parentWidth = parentBounds.width(); + final int parentHeight = parentBounds.height(); + float aspect = Math.max(parentWidth, parentHeight) + / (float) Math.min(parentWidth, parentHeight); + + // Adjust the fixed orientation letterbox bounds to fit the app request aspect ratio in + // order to use the extra available space. + final float maxAspectRatio = info.maxAspectRatio; + final float minAspectRatio = info.minAspectRatio; + if (aspect > maxAspectRatio && maxAspectRatio != 0) { + aspect = maxAspectRatio; + } else if (aspect < minAspectRatio) { + aspect = minAspectRatio; + } + + // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. + // TODO(b/175212232): Rename getTaskLetterboxAspectRatio and all related methods since fixed + // orientation letterbox is on the activity level now. + final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); + // Activity min/max aspect ratio restrictions will be respected by the activity-level + // letterboxing (size-compat mode). Therefore this override can control the maximum screen + // area that can be occupied by the app in the letterbox mode. + aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO + ? letterboxAspectRatioOverride : aspect; + + // Store the current bounds to be able to revert to size compat mode values below if needed. + Rect mTmpFullBounds = new Rect(resolvedBounds); + if (forcedOrientation == ORIENTATION_LANDSCAPE) { + final int height = (int) Math.rint(parentWidth / aspect); + final int top = parentBounds.centerY() - height / 2; + resolvedBounds.set(parentBounds.left, top, parentBounds.right, top + height); + } else { + final int width = (int) Math.rint(parentHeight / aspect); + final int left = parentBounds.centerX() - width / 2; + resolvedBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); + } + + if (mCompatDisplayInsets != null) { + mCompatDisplayInsets.getBoundsByRotation( + mTmpBounds, newParentConfig.windowConfiguration.getRotation()); + if (resolvedBounds.width() != mTmpBounds.width() + || resolvedBounds.height() != mTmpBounds.height()) { + // The app shouldn't be resized, we only do fixed orientation letterboxing if the + // compat bounds are also from the same fixed orientation letterbox. Otherwise, + // clear the fixed orientation bounds to show app in size compat mode. + resolvedBounds.set(mTmpFullBounds); + return; + } + } + + // Calculate app bounds using fixed orientation bounds because they will be needed later + // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. + task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig); + mIsLetterboxedForFixedOrientationAndAspectRatio = true; + } + /** * Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by * aspect ratio, the position will be centered horizontally in parent's app bounds to balance @@ -7020,6 +7100,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration) { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); + + // When an activity needs to be letterboxed because of fixed orientation, use fixed + // orientation bounds (stored in resolved bounds) instead of parent bounds since the + // activity will be displayed within them even if it is in size compat mode. They should be + // saved here before resolved bounds are overridden below. + final Rect containerBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(resolvedBounds) + : newParentConfiguration.windowConfiguration.getBounds(); + final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio() + ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()) + : newParentConfiguration.windowConfiguration.getAppBounds(); + final int requestedOrientation = getRequestedConfigurationOrientation(); final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; final int orientation = orientationRequested @@ -7078,7 +7170,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Below figure is an example that puts an activity which was launched in a larger container // into a smaller container. // The outermost rectangle is the real display bounds. - // "@" is the parent app bounds. + // "@" is the container app bounds (parent bounds or fixed orientation bouds) // "#" is the {@code resolvedBounds} that applies to application. // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. // ------------------------------ @@ -7094,19 +7186,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The application is still layouted in "#" since it was launched, and it will be visually // scaled and positioned to "*". + final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); + // Calculates the scale and offset to horizontal center the size compatibility bounds into // the region which is available to application. - final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); - final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds(); - final Rect resolvedAppBounds = resolvedConfig.windowConfiguration.getAppBounds(); final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); - final int viewportW = parentAppBounds.width(); - final int viewportH = parentAppBounds.height(); + final int viewportW = containerAppBounds.width(); + final int viewportH = containerAppBounds.height(); // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); - final int screenTopInset = parentAppBounds.top - parentBounds.top; + final int screenTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = screenTopInset != resolvedAppBounds.top - resolvedBounds.top; if (mSizeCompatScale != 1f || topNotAligned) { if (mSizeCompatBounds == null) { @@ -7126,8 +7217,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int offsetX = getHorizontalCenterOffset( (int) viewportW, (int) (contentW * mSizeCompatScale)); // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final int screenPosX = (fillContainer ? parentBounds.left : parentAppBounds.left) + offsetX; - final int screenPosY = parentBounds.top; + final int screenPosX = (fillContainer + ? containerBounds.left : containerAppBounds.left) + offsetX; + final int screenPosY = containerBounds.top; if (screenPosX != 0 || screenPosY != 0) { if (mSizeCompatBounds != null) { mSizeCompatBounds.offset(screenPosX, screenPosY); @@ -7137,6 +7229,52 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int dy = screenPosY - resolvedBounds.top; offsetBounds(resolvedConfig, dx, dy); } + + mInSizeCompatModeForBounds = + isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); + } + + private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { + final int appWidth = appBounds.width(); + final int appHeight = appBounds.height(); + final int containerAppWidth = containerBounds.width(); + final int containerAppHeight = containerBounds.height(); + + if (containerAppWidth == appWidth && containerAppHeight == appHeight) { + // Matched the container bounds. + return false; + } + if (containerAppWidth > appWidth && containerAppHeight > appHeight) { + // Both sides are smaller than the container. + return true; + } + if (containerAppWidth < appWidth || containerAppHeight < appHeight) { + // One side is larger than the container. + return true; + } + + // 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.maxAspectRatio > 0) { + final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) + / Math.min(appWidth, appHeight); + if (aspectRatio >= info.maxAspectRatio) { + // The current size has reached the max aspect ratio. + return false; + } + } + if (info.minAspectRatio > 0) { + // The activity should have at least the min aspect ratio, so this checks if the + // container still has available space to provide larger aspect ratio. + final float containerAspectRatio = + (0.5f + Math.max(containerAppWidth, containerAppHeight)) + / Math.min(containerAppWidth, containerAppHeight); + if (containerAspectRatio <= info.minAspectRatio) { + // The long side has reached the parent. + return false; + } + } + return true; } /** @return The horizontal offset of putting the content in the center of viewport. */ @@ -7288,7 +7426,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Task rootTask = getRootTask(); final float minAspectRatio = info.minAspectRatio; - if (task == null || rootTask == null || (inMultiWindowMode() && !shouldUseSizeCompatMode()) + if (task == null || rootTask == null + || (inMultiWindowMode() && !shouldCreateCompatDisplayInsets()) || (maxAspectRatio == 0 && minAspectRatio == 0) || isInVrUiMode(getConfiguration())) { // We don't enforce aspect ratio if the activity task is in multiwindow unless it @@ -7426,8 +7565,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (displayChanged) { mLastReportedDisplayId = newDisplayId; } - // TODO(b/36505427): Is there a better place to do this? - updateSizeCompatMode(); // Short circuit: if the two full configurations are equal (the common case), then there is // nothing to do. We test the full configuration instead of the global and merged override @@ -7706,11 +7843,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Reset the existing override configuration so it can be updated according to the latest // configuration. clearSizeCompatMode(); - if (mVisibleRequested) { - // Configuration will be ensured when becoming visible, so if it is already visible, - // then the manual update is needed. - updateSizeCompatMode(); - } if (!attachedToProcess()) { return; @@ -8117,8 +8249,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private final int mHeight; /** Whether the {@link Task} windowingMode represents a floating window*/ final boolean mIsFloating; - /** Whether the {@link Task} is letterboxed when the unresizable activity is first shown. */ - final boolean mIsTaskLetterboxed; + /** + * Whether is letterboxed because of fixed orientation when the unresizable activity is + * first shown. + */ + final boolean mIsInFixedOrientationLetterbox; /** * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It * is used to compute the appBounds. @@ -8132,7 +8267,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect[] mStableInsets = new Rect[4]; /** Constructs the environment to simulate the bounds behavior of the given container. */ - CompatDisplayInsets(DisplayContent display, ActivityRecord container) { + CompatDisplayInsets(DisplayContent display, ActivityRecord container, + @Nullable Rect fixedOrientationBounds) { mIsFloating = container.getWindowConfiguration().tasksAreFloating(); if (mIsFloating) { final Rect containerBounds = container.getWindowConfiguration().getBounds(); @@ -8145,24 +8281,34 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mNonDecorInsets[rotation] = emptyRect; mStableInsets[rotation] = emptyRect; } - mIsTaskLetterboxed = false; + mIsInFixedOrientationLetterbox = false; return; } final Task task = container.getTask(); - mIsTaskLetterboxed = task != null && task.isTaskLetterboxed(); + + mIsInFixedOrientationLetterbox = fixedOrientationBounds != null; // Store the bounds of the Task for the non-resizable activity to use in size compat // mode so that the activity will not be resized regardless the windowing mode it is // currently in. - final WindowContainer filledContainer = task != null ? task : display; - final Point dimensions = getRotationZeroDimensions(filledContainer); + // When an activity needs to be letterboxed because of fixed orientation, use fixed + // orientation bounds instead of task bounds since the activity will be displayed + // within these even if it is in size compat mode. + final Rect filledContainerBounds = mIsInFixedOrientationLetterbox + ? fixedOrientationBounds + : task != null ? task.getBounds() : display.getBounds(); + final int filledContainerRotation = task != null + ? task.getConfiguration().windowConfiguration.getRotation() + : display.getConfiguration().windowConfiguration.getRotation(); + final Point dimensions = getRotationZeroDimensions( + filledContainerBounds, filledContainerRotation); mWidth = dimensions.x; mHeight = dimensions.y; // Bounds of the filled container if it doesn't fill the display. final Rect unfilledContainerBounds = - filledContainer.getBounds().equals(display.getBounds()) ? null : new Rect(); + filledContainerBounds.equals(display.getBounds()) ? null : new Rect(); final DisplayPolicy policy = display.getDisplayPolicy(); for (int rotation = 0; rotation < 4; rotation++) { mNonDecorInsets[rotation] = new Rect(); @@ -8182,9 +8328,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // The insets is based on the display, but the container may be smaller than the // display, so update the insets to exclude parts that are not intersected with the // container. - unfilledContainerBounds.set(filledContainer.getBounds()); + unfilledContainerBounds.set(filledContainerBounds); display.rotateBounds( - filledContainer.getConfiguration().windowConfiguration.getRotation(), + filledContainerRotation, rotation, unfilledContainerBounds); updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]); @@ -8197,9 +8343,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * the display is rotated, we can calculate the bounds by rotating the dimensions. * @see #getBoundsByRotation */ - private static Point getRotationZeroDimensions(WindowContainer container) { - final Rect bounds = container.getBounds(); - final int rotation = container.getConfiguration().windowConfiguration.getRotation(); + private static Point getRotationZeroDimensions(final Rect bounds, int rotation) { final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int width = bounds.width(); final int height = bounds.height(); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 8bd4dfd054b6e..34ddcdd209e3d 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -35,7 +35,6 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; -import static android.app.WindowConfiguration.isSplitScreenWindowingMode; import static android.app.WindowConfiguration.windowingModeToString; import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; @@ -148,7 +147,6 @@ import static com.android.server.wm.WindowContainerChildProto.TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT; import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; -import static com.android.server.wm.WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO; import static com.android.server.wm.WindowManagerService.dipToPixel; import static com.android.server.wm.WindowStateAnimator.ROOT_TASK_CLIP_BEFORE_ANIM; @@ -597,10 +595,6 @@ class Task extends WindowContainer { @Nullable private ActivityRecord mResumedActivity = null; - /** Last activity that is used to compute the Task bounds. */ - @Nullable - private ActivityRecord mLastTaskBoundsComputeActivity; - private boolean mForceShowForAllUsers; /** When set, will force the task to report as invisible. */ @@ -1496,11 +1490,6 @@ class Task extends WindowContainer { } void cleanUpActivityReferences(ActivityRecord r) { - // mLastTaskBoundsComputeActivity is set at leaf Task - if (mLastTaskBoundsComputeActivity == r) { - mLastTaskBoundsComputeActivity = null; - } - // mPausingActivity is set at leaf task if (mPausingActivity != null && mPausingActivity == r) { mPausingActivity = null; @@ -2865,7 +2854,6 @@ class Task extends WindowContainer { private void resolveLeafOnlyOverrideConfigs(Configuration newParentConfig, Rect previousBounds) { - mLastTaskBoundsComputeActivity = getTopNonFinishingActivity(false /* includeOverlays */); int windowingMode = getResolvedOverrideConfiguration().windowConfiguration.getWindowingMode(); @@ -2879,7 +2867,8 @@ class Task extends WindowContainer { getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - computeFullscreenBounds(outOverrideBounds, newParentConfig); + // Use empty bounds to indicate "fill parent". + outOverrideBounds.setEmpty(); // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if // the parent or display is smaller than the size, the content may be cropped. return; @@ -2890,21 +2879,6 @@ class Task extends WindowContainer { computeFreeformBounds(outOverrideBounds, newParentConfig); return; } - - if (isSplitScreenWindowingMode(windowingMode) - || windowingMode == WINDOWING_MODE_MULTI_WINDOW) { - // This is to compute whether the task should be letterboxed to handle non-resizable app - // in multi window. There is no split screen only logic. - computeLetterboxBounds(outOverrideBounds, newParentConfig); - } - } - - /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */ - @VisibleForTesting - void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) { - // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent". - outBounds.setEmpty(); - computeLetterboxBounds(outBounds, newParentConfig); } /** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */ @@ -2936,94 +2910,6 @@ class Task extends WindowContainer { } } - /** - * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation - * change and the requested orientation is different from the parent. - */ - private void computeLetterboxBounds(@NonNull Rect outBounds, - @NonNull Configuration newParentConfig) { - if (handlesOrientationChangeFromDescendant()) { - // No need to letterbox at task level. Display will handle fixed-orientation requests. - return; - } - - final int parentOrientation = newParentConfig.orientation; - // Use the top activity as the reference of orientation. Don't include overlays because - // it is usually not the actual content or just temporarily shown. - // E.g. ForcedResizableInfoActivity. - final ActivityRecord refActivity = getTopNonFinishingActivity(false /* includeOverlays */); - - // If the task or the reference activity requires a different orientation (either by - // override or activityInfo), make it fit the available bounds by scaling down its bounds. - final int overrideOrientation = getRequestedOverrideConfiguration().orientation; - final int forcedOrientation = - (overrideOrientation != ORIENTATION_UNDEFINED || refActivity == null) - ? overrideOrientation : refActivity.getRequestedConfigurationOrientation(); - if (forcedOrientation == ORIENTATION_UNDEFINED || forcedOrientation == parentOrientation) { - return; - } - - final ActivityRecord.CompatDisplayInsets compatDisplayInsets = - refActivity == null ? null : refActivity.getCompatDisplayInsets(); - if (compatDisplayInsets != null && !compatDisplayInsets.mIsTaskLetterboxed) { - // App prefers to keep its original size. - // If the size compat is from previous task letterboxing, we may want to have task - // letterbox again, otherwise it will show the size compat restart button even if the - // restart bounds will be the same. - return; - } - - final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); - final int parentWidth = parentBounds.width(); - final int parentHeight = parentBounds.height(); - float aspect = Math.max(parentWidth, parentHeight) - / (float) Math.min(parentWidth, parentHeight); - - // Adjust the Task letterbox bounds to fit the app request aspect ratio in order to use the - // extra available space. - if (refActivity != null) { - final float maxAspectRatio = refActivity.info.maxAspectRatio; - final float minAspectRatio = refActivity.info.minAspectRatio; - if (aspect > maxAspectRatio && maxAspectRatio != 0) { - aspect = maxAspectRatio; - } else if (aspect < minAspectRatio) { - aspect = minAspectRatio; - } - } - - // Override from config_letterboxAspectRatio or via ADB with set-letterbox-aspect-ratio. - final float letterboxAspectRatioOverride = mWmService.getTaskLetterboxAspectRatio(); - // Activity min/max aspect ratio restrictions will be respected by the activity-level - // letterboxing (size-compat mode). Therefore this override can control the maximum screen - // area that can be occupied by the app in the letterbox mode. - aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO - ? letterboxAspectRatioOverride : aspect; - - // Store the current bounds to be able to revert to size compat mode values below if needed. - mTmpFullBounds.set(outBounds); - if (forcedOrientation == ORIENTATION_LANDSCAPE) { - final int height = (int) Math.rint(parentWidth / aspect); - final int top = parentBounds.centerY() - height / 2; - outBounds.set(parentBounds.left, top, parentBounds.right, top + height); - } else { - final int width = (int) Math.rint(parentHeight / aspect); - final int left = parentBounds.centerX() - width / 2; - outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); - } - - if (compatDisplayInsets != null) { - compatDisplayInsets.getBoundsByRotation( - mTmpBounds, newParentConfig.windowConfiguration.getRotation()); - if (outBounds.width() != mTmpBounds.width() - || outBounds.height() != mTmpBounds.height()) { - // The app shouldn't be resized, we only do task letterboxing if the compat bounds - // is also from the same task letterbox. Otherwise, clear the task bounds to show - // app in size compat mode. - outBounds.set(mTmpFullBounds); - } - } - } - Rect updateOverrideConfigurationFromLaunchBounds() { // If the task is controlled by another organized task, do not set override // configurations and let its parent (organized task) to control it; @@ -3038,11 +2924,6 @@ class Task extends WindowContainer { return bounds; } - @Nullable - ActivityRecord getLastTaskBoundsComputeActivity() { - return mLastTaskBoundsComputeActivity; - } - /** Updates the task's bounds and override configuration to match what is expected for the * input root task. */ void updateOverrideConfigurationForRootTask(Task inRootTask) { @@ -3941,12 +3822,6 @@ class Task extends WindowContainer { || activityType == ACTIVITY_TYPE_ASSISTANT; } - boolean isTaskLetterboxed() { - // No letterbox for multi window root task - return !matchParentBounds() - && (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask()); - } - @Override boolean fillsParent() { // From the perspective of policy, we still want to report that this task fills parent diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index a94b0aa9b72fa..4657cd0e7b252 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3821,13 +3821,21 @@ class WindowState extends WindowContainer implements WindowManagerP /** @return true when the window should be letterboxed. */ boolean isLetterboxedAppWindow() { // Fullscreen mode but doesn't fill display area. - return (!inMultiWindowMode() && !matchesDisplayAreaBounds()) - // Activity in size compat. - || (mActivityRecord != null && mActivityRecord.inSizeCompatMode()) - // Task letterboxed. - || (getTask() != null && getTask().isTaskLetterboxed()) - // Letterboxed for display cutout. - || isLetterboxedForDisplayCutout(); + if (!inMultiWindowMode() && !matchesDisplayAreaBounds()) { + return true; + } + if (mActivityRecord != null) { + // Activity in size compat. + if (mActivityRecord.inSizeCompatMode()) { + return true; + } + // Letterbox for fixed orientation. + if (mActivityRecord.isLetterboxedForFixedOrientationAndAspectRatio()) { + return true; + } + } + // Letterboxed for display cutout. + return isLetterboxedForDisplayCutout(); } /** Returns {@code true} if the window is letterboxed for the display cutout. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index aa1110cd55a76..40680df1fcf3d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -555,9 +555,10 @@ public class ActivityRecordTests extends WindowTestsBase { activity.setRequestedOrientation( isScreenPortrait ? SCREEN_ORIENTATION_PORTRAIT : SCREEN_ORIENTATION_LANDSCAPE); - // Asserts it has orientation derived from bounds. - assertEquals(isScreenPortrait ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT, + // Asserts it has orientation derived requested orientation (fixed orientation letterbox). + assertEquals(isScreenPortrait ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE, activity.getConfiguration().orientation); + assertTrue(activity.isLetterboxedForFixedOrientationAndAspectRatio()); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java index f91c9d0e9853b..e9c356d6c6c40 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java @@ -171,7 +171,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect activityBounds = new Rect(mFirstActivity.getBounds()); // DAG is portrait (860x1200), so Task and Activity fill DAG. - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); assertThat(taskBounds).isEqualTo(dagBounds); assertThat(activityBounds).isEqualTo(taskBounds); @@ -194,8 +194,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect activityConfigBounds = new Rect(mFirstActivity.getConfiguration().windowConfiguration.getBounds()); - // DAG is landscape (1200x860), Task fills parent - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + // DAG is landscape (1200x860), no fixed orientation letterbox + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); @@ -211,7 +211,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test - public void testLaunchLandscapeApp_taskIsLetterboxInDisplayAreaGroup() { + public void testLaunchLandscapeApp_activityIsLetterboxForFixedOrientationInDisplayAreaGroup() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); @@ -221,17 +221,18 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect taskBounds = new Rect(mFirstTask.getBounds()); final Rect activityBounds = new Rect(mFirstActivity.getBounds()); - // DAG is portrait (860x1200), so Task is letterbox (860x[860x860/1200=616]) - assertThat(mFirstTask.isTaskLetterboxed()).isTrue(); + // DAG is portrait (860x1200), and activity is letterboxed for fixed orientation + // (860x[860x860/1200=616]). Task fills DAG. + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); - assertThat(taskBounds.width()).isEqualTo(dagBounds.width()); - assertThat(taskBounds.height()) + assertThat(taskBounds).isEqualTo(dagBounds); + assertThat(activityBounds.width()).isEqualTo(dagBounds.width()); + assertThat(activityBounds.height()) .isEqualTo(dagBounds.width() * dagBounds.width() / dagBounds.height()); - assertThat(activityBounds).isEqualTo(taskBounds); } @Test - public void testLaunchLandscapeApp_taskLetterboxBecomesActivityLetterboxAfterRotation() { + public void testLaunchLandscapeApp_fixedOrientationLetterboxBecomesSizeCompatAfterRotation() { mFirstRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda); @@ -245,9 +246,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { final Rect newTaskBounds = new Rect(mFirstTask.getBounds()); final Rect newActivityBounds = new Rect(mFirstActivity.getBounds()); - // DAG is landscape (1200x860), Task fills parent - // Task letterbox size - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + // DAG is landscape (1200x860), no fixed orientation letterbox + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isTrue(); assertThat(newDagBounds.width()).isEqualTo(dagBounds.height()); assertThat(newDagBounds.height()).isEqualTo(dagBounds.width()); @@ -311,7 +311,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { } @Test - public void testResizableFixedOrientationApp_taskLevelLetterboxing() { + public void testResizableFixedOrientationApp_fixedOrientationLetterboxing() { mFirstRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); mSecondRoot.setIgnoreOrientationRequest(false /* ignoreOrientationRequest */); @@ -324,7 +324,7 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_LANDSCAPE); assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); - assertThat(mFirstTask.isTaskLetterboxed()).isFalse(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); // Launch portrait on second DAG @@ -336,13 +336,13 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase { assertThat(mDisplay.getLastOrientation()).isEqualTo(SCREEN_ORIENTATION_PORTRAIT); assertThat(mSecondRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(mSecondActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); - assertThat(mSecondTask.isTaskLetterboxed()).isFalse(); + assertThat(mSecondActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isFalse(); assertThat(mSecondActivity.inSizeCompatMode()).isFalse(); // First activity is letterboxed in portrait as requested. assertThat(mFirstRoot.getConfiguration().orientation).isEqualTo(ORIENTATION_LANDSCAPE); assertThat(mFirstActivity.getConfiguration().orientation).isEqualTo(ORIENTATION_PORTRAIT); - assertThat(mFirstTask.isTaskLetterboxed()).isTrue(); + assertThat(mFirstActivity.isLetterboxedForFixedOrientationAndAspectRatio()).isTrue(); assertThat(mFirstActivity.inSizeCompatMode()).isFalse(); } 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 cc4d4eaa9e8b6..a5d14e6845df0 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 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.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; @@ -43,7 +44,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.same; @@ -126,6 +126,7 @@ public class SizeCompatTests extends WindowTestsBase { // Put app window into freeform and then make it a compat app. final Rect bounds = new Rect(100, 100, 400, 600); mTask.setBounds(bounds); + prepareUnresizable(mActivity, -1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT); assertEquals(bounds, mActivity.getBounds()); @@ -194,12 +195,7 @@ public class SizeCompatTests extends WindowTestsBase { new TestDisplayContent.Builder(mAtm, 1000, 2000) .setDensityDpi(200).build(); - mActivity = new ActivityBuilder(mAtm) - .setTask(mTask) - .setResizeMode(RESIZE_MODE_UNRESIZEABLE) - .setMaxAspectRatio(1.5f) - .build(); - mActivity.mVisibleRequested = true; + prepareUnresizable(mActivity, 1.5f /* maxAspect */, SCREEN_ORIENTATION_UNSPECIFIED); final Rect originalBounds = new Rect(mActivity.getBounds()); final int originalDpi = mActivity.getConfiguration().densityDpi; @@ -527,18 +523,18 @@ public class SizeCompatTests extends WindowTestsBase { .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) .build(); - assertTrue(activity.shouldUseSizeCompatMode()); + assertTrue(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because it is on a resizable task // in multi-window mode. mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); // The non-resizable activity should not be size compat because the display support // changing windowing mode from fullscreen to freeform. mTask.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); mTask.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -558,7 +554,7 @@ public class SizeCompatTests extends WindowTestsBase { SizeCompatTests.class.getName())) .setUid(android.os.Process.myUid()) .build(); - assertFalse(activity.shouldUseSizeCompatMode()); + assertFalse(activity.shouldCreateCompatDisplayInsets()); } @Test @@ -614,7 +610,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedInTaskLetterbox() { + public void testDisplayIgnoreOrientationRequest_fixedOrientationAppLaunchedLetterbox() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -623,7 +619,6 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds()); - final Rect taskBounds = new Rect(mTask.getBounds()); final Rect activityBounds = new Rect(mActivity.getBounds()); // Display shouldn't be rotated. @@ -631,19 +626,19 @@ public class SizeCompatTests extends WindowTestsBase { mActivity.mDisplayContent.getLastOrientation()); assertTrue(displayBounds.width() > displayBounds.height()); - // App should launch in task level letterboxing. - assertTrue(mTask.isTaskLetterboxed()); + // App should launch in fixed orientation letterbox. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(taskBounds, activityBounds); - // Task bounds should be 700x1400 with the ratio as the display. - assertEquals(displayBounds.height(), taskBounds.height()); + // Activity bounds should be 700x1400 with the ratio as the display. + assertEquals(displayBounds.height(), activityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - taskBounds.width()); + activityBounds.width()); } @Test - public void testDisplayIgnoreOrientationRequest_taskLetterboxBecameSizeCompatAfterRotate() { + public void + testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); @@ -661,7 +656,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(displayBounds.width() < displayBounds.height()); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); assertEquals(activityBounds.width(), newActivityBounds.width()); assertEquals(activityBounds.height(), newActivityBounds.height()); @@ -680,7 +675,7 @@ public class SizeCompatTests extends WindowTestsBase { Rect activityBounds = new Rect(mActivity.getBounds()); // App should launch in fullscreen. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); assertEquals(displayBounds, activityBounds); @@ -692,7 +687,7 @@ public class SizeCompatTests extends WindowTestsBase { assertTrue(displayBounds.width() > displayBounds.height()); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // App bounds should be 700x1400 with the ratio as the display. @@ -702,7 +697,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test - public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInTaskLetterbox() { + public void testDisplayIgnoreOrientationRequest_newLaunchedOrientationAppInLetterbox() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); final DisplayContent display = mActivity.mDisplayContent; @@ -711,7 +706,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Launch another portrait fixed app. @@ -726,19 +721,19 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - // Task and app bounds should be 700x1400 with the ratio as the display. - assertTrue(mTask.isTaskLetterboxed()); + // Task and display bounds should be equal while activity should be letterboxed and + // has 700x1400 bounds with the ratio as the display. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds, newActivityBounds); - assertEquals(displayBounds.height(), taskBounds.height()); + assertEquals(taskBounds, displayBounds); + assertEquals(displayBounds.height(), newActivityBounds.height()); assertEquals(displayBounds.height() * displayBounds.height() / displayBounds.width(), - taskBounds.width()); + newActivityBounds.width()); } @Test @@ -751,7 +746,7 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app without max aspect. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Launch another portrait fixed app with max aspect ratio as 1.3. @@ -767,21 +762,20 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); final Rect newActivityBounds = new Rect(newActivity.getBounds()); - // Task bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. - assertTrue(mTask.isTaskLetterboxed()); - assertEquals(displayBounds.height(), taskBounds.height()); - assertEquals((long) Math.rint(taskBounds.height() / newActivity.info.maxAspectRatio), - taskBounds.width()); + // Task bounds should fill parent bounds. + assertEquals(displayBounds, taskBounds); - // App bounds should be fullscreen in Task bounds. + // Activity bounds should be (1400 / 1.3 = 1076)x1400 with the app requested ratio. + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(newActivity.inSizeCompatMode()); - assertEquals(taskBounds, newActivityBounds); + assertEquals(displayBounds.height(), newActivityBounds.height()); + assertEquals((long) Math.rint(newActivityBounds.height() / newActivity.info.maxAspectRatio), + newActivityBounds.width()); } @Test @@ -795,26 +789,23 @@ public class SizeCompatTests extends WindowTestsBase { prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); clearInvocations(mActivity); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); // Rotate display to portrait. rotateDisplay(mActivity.mDisplayContent, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); final Rect activityBounds = new Rect(mActivity.getBounds()); mTask.resumeTopActivityUncheckedLocked(null /* prev */, null /* options */); // App still in size compat, and the bounds don't change. verify(mActivity, never()).clearSizeCompatMode(); - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); - assertEquals(mTask.getLastTaskBoundsComputeActivity(), mActivity); assertEquals(activityBounds, mActivity.getBounds()); } @@ -828,22 +819,22 @@ public class SizeCompatTests extends WindowTestsBase { // Portrait fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); } @@ -859,22 +850,22 @@ public class SizeCompatTests extends WindowTestsBase { // Landscape fixed app. prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); // Rotate display to portrait. rotateDisplay(display, ROTATION_90); // App should be in size compat. - assertFalse(mTask.isTaskLetterboxed()); + assertFalse(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertScaled(); // Rotate display to landscape. rotateDisplay(display, ROTATION_180); - // In Task letterbox - assertTrue(mTask.isTaskLetterboxed()); + // In fixed orientation letterbox + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); assertFalse(mActivity.inSizeCompatMode()); } @@ -933,21 +924,21 @@ public class SizeCompatTests extends WindowTestsBase { addWindowToActivity(mActivity); mActivity.mRootWindowContainer.performSurfacePlacement(); - // Split screen is also in portrait [1000,1400], so Task should be in letterbox, and - // activity fills task. - assertEquals(ORIENTATION_LANDSCAPE, mTask.getConfiguration().orientation); + // Split screen is also in portrait [1000,1400], so activty should be in fixed orientation + // letterbox. + assertEquals(ORIENTATION_PORTRAIT, mTask.getConfiguration().orientation); assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation); assertFitted(); - assertTrue(mTask.isTaskLetterboxed()); + assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio()); - // Letterbox should fill the gap between the split screen and the letterboxed task. + // Letterbox should fill the gap between the split screen and the letterboxed activity. final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds()); - final Rect letterboxedTaskBounds = new Rect(mTask.getBounds()); - assertTrue(primarySplitBounds.contains(letterboxedTaskBounds)); - assertEquals(new Rect(letterboxedTaskBounds.left - primarySplitBounds.left, - letterboxedTaskBounds.top - primarySplitBounds.top, - primarySplitBounds.right - letterboxedTaskBounds.right, - primarySplitBounds.bottom - letterboxedTaskBounds.bottom), + final Rect letterboxedBounds = new Rect(mActivity.getBounds()); + assertTrue(primarySplitBounds.contains(letterboxedBounds)); + assertEquals(new Rect(letterboxedBounds.left - primarySplitBounds.left, + letterboxedBounds.top - primarySplitBounds.top, + primarySplitBounds.right - letterboxedBounds.right, + primarySplitBounds.bottom - letterboxedBounds.bottom), mActivity.getLetterboxInsets()); } diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index 0eb8c8d2e58a0..d853b930af115 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -266,8 +266,9 @@ public class TaskRecordTests extends WindowTestsBase { root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); assertEquals(root, task.getRootActivity()); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getRootActivity().getOrientation()); - assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); - assertEquals(fullScreenBounds.height(), task.getBounds().height()); + // Portrait orientation is enforced on activity level. Task should fill fullscreen bounds. + assertThat(task.getBounds().height()).isLessThan(task.getBounds().width()); + assertEquals(fullScreenBounds, task.getBounds()); // Top activity gets used final ActivityRecord top = new ActivityBuilder(mAtm).setTask(task).setParentTask(stack) @@ -286,8 +287,11 @@ public class TaskRecordTests extends WindowTestsBase { // Fix the display orientation to portrait which is 90 degrees for the test display. dr.setUserRotation(USER_ROTATION_FREE, ROTATION_90); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); + // Fixed orientation request should be resolved on activity level. Task fills display + // bounds. + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); // in FREEFORM, no constraint final Rect freeformBounds = new Rect(display.getBounds()); @@ -297,10 +301,11 @@ public class TaskRecordTests extends WindowTestsBase { task.setBounds(freeformBounds); assertEquals(freeformBounds, task.getBounds()); - // FULLSCREEN letterboxes bounds + // FULLSCREEN letterboxes bounds on activity level, no constraint on task level. stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN); - assertThat(task.getBounds().width()).isGreaterThan(task.getBounds().height()); - assertEquals(fullScreenBoundsPort.width(), task.getBounds().width()); + assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width()); + assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height()); + assertEquals(fullScreenBoundsPort, task.getBounds()); // FREEFORM restores bounds as before stack.setWindowingMode(WINDOWING_MODE_FREEFORM); @@ -327,9 +332,10 @@ public class TaskRecordTests extends WindowTestsBase { assertEquals(fullScreenBounds, task.getBounds()); - // Setting app to fixed portrait fits within parent + // Setting app to fixed portrait fits within parent on activity level. Task fills parent. root.setRequestedOrientation(SCREEN_ORIENTATION_PORTRAIT); - assertThat(task.getBounds().width()).isLessThan(task.getBounds().height()); + assertThat(root.getBounds().width()).isLessThan(root.getBounds().height()); + assertEquals(task.getBounds(), fullScreenBounds); assertEquals(SCREEN_ORIENTATION_PORTRAIT, task.getOrientation()); } @@ -424,7 +430,8 @@ public class TaskRecordTests extends WindowTestsBase { // to the input bounds. final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build(); final ActivityRecord.CompatDisplayInsets compatIntsets = - new ActivityRecord.CompatDisplayInsets(display, activity); + new ActivityRecord.CompatDisplayInsets( + display, activity, /* fixedOrientationBounds= */ null); task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets); assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 1c0f640e1a9c3..c3eb5c49cea04 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -257,24 +257,4 @@ public class TaskTests extends WindowTestsBase { task.resolveOverrideConfiguration(parentConfig); assertThat(resolvedOverride.getWindowingMode()).isEqualTo(WINDOWING_MODE_UNDEFINED); } - - @Test - public void testCleanUpActivityReferences_clearLastTaskBoundsComputeActivity() { - final Task rootTask = createTaskStackOnDisplay(mDisplayContent); - final Task leafTask = createTaskInStack(rootTask, 0 /* userId */); - final ActivityRecord activity2 = createActivityRecord(mDisplayContent, leafTask); - final ActivityRecord activity1 = createActivityRecord(mDisplayContent, leafTask); - activity1.finishing = false; - leafTask.resolveOverrideConfiguration(rootTask.getConfiguration()); - - assertEquals(activity1, leafTask.getLastTaskBoundsComputeActivity()); - - leafTask.cleanUpActivityReferences(activity2); - - assertNotNull(leafTask.getLastTaskBoundsComputeActivity()); - - leafTask.cleanUpActivityReferences(activity1); - - assertNull(leafTask.getLastTaskBoundsComputeActivity()); - } }