diff options
9 files changed, 519 insertions, 385 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 5ba8433146dc..6bf70f0a471d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -226,7 +226,6 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_F import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE; import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.wm.ActivityTaskManagerService.getInputDispatchingTimeoutMillisLocked; -import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -637,12 +636,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A private SizeConfigurationBuckets mSizeConfigurations; - /** - * The precomputed display insets for resolving configuration. It will be non-null if - * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}. - */ - private AppCompatDisplayInsets mAppCompatDisplayInsets; - @VisibleForTesting final TaskFragment.ConfigOverrideHint mResolveConfigHint; @@ -792,22 +785,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @NonNull final AppCompatController mAppCompatController; - /** - * The scale to fit at least one side of the activity to its parent. If the activity uses - * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. - */ - private float mSizeCompatScale = 1f; - - /** - * The bounds in global coordinates for activity in size compatibility mode. - * @see ActivityRecord#hasSizeCompatBounds() - */ - 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 the activity is eligible to be letterboxed for fixed orientation with respect to its // requested orientation, even when it's letterbox for another reason (e.g., size compat mode) // and therefore #isLetterboxedForFixedOrientationAndAspectRatio returns false. @@ -1257,10 +1234,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mPendingRelaunchCount != 0) { pw.print(prefix); pw.print("mPendingRelaunchCount="); pw.println(mPendingRelaunchCount); } - if (mSizeCompatScale != 1f || mSizeCompatBounds != null) { - pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds=" - + mSizeCompatBounds); - } if (mRemovingFromDisplay) { pw.println(prefix + "mRemovingFromDisplay=" + mRemovingFromDisplay); } @@ -6439,7 +6412,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTaskSupervisor.mStoppingActivities.remove(this); if (getDisplayArea().allResumedActivitiesComplete()) { // Construct the compat environment at a relatively stable state if needed. - updateAppCompatDisplayInsets(); + mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets(); mRootWindowContainer.executeAppTransitionForAllDisplay(); } @@ -8072,7 +8045,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A != getRequestedConfigurationOrientation(false /*forDisplay */)) { // Do not change the requested configuration now, because this will be done when setting // the orientation below with the new mAppCompatDisplayInsets - clearSizeCompatModeAttributes(); + mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatModeAttributes(); } ProtoLog.v(WM_DEBUG_ORIENTATION, "Setting requested orientation %s for %s", @@ -8205,18 +8178,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Nullable AppCompatDisplayInsets getAppCompatDisplayInsets() { - if (mAppCompatController.getTransparentPolicy().isRunning()) { - return mAppCompatController.getTransparentPolicy().getInheritedAppCompatDisplayInsets(); - } - return mAppCompatDisplayInsets; - } - - /** - * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without - * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()} - */ - boolean hasAppCompatDisplayInsetsWithoutInheritance() { - return mAppCompatDisplayInsets != null; + return mAppCompatController.getAppCompatSizeCompatModePolicy().getAppCompatDisplayInsets(); } /** @@ -8224,7 +8186,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A * density than its parent or its bounds don't fit in parent naturally. */ boolean inSizeCompatMode() { - if (mInSizeCompatModeForBounds) { + final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController + .getAppCompatSizeCompatModePolicy(); + if (scmPolicy.isInSizeCompatModeForBounds()) { return true; } if (getAppCompatDisplayInsets() == null || !shouldCreateAppCompatDisplayInsets() @@ -8321,69 +8285,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override boolean hasSizeCompatBounds() { - return mSizeCompatBounds != null; - } - - // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. - private void updateAppCompatDisplayInsets() { - if (getAppCompatDisplayInsets() != null || !shouldCreateAppCompatDisplayInsets()) { - // The override configuration is set only once in size compatibility mode. - return; - } - - Configuration overrideConfig = getRequestedOverrideConfiguration(); - final Configuration fullConfig = getConfiguration(); - - // Ensure the screen related fields are set. It is used to prevent activity relaunch - // when moving between displays. For screenWidthDp and screenWidthDp, because they - // are relative to bounds and density, they will be calculated in - // {@link Task#computeConfigResourceOverrides} and the result will also be - // relatively fixed. - overrideConfig.colorMode = fullConfig.colorMode; - overrideConfig.densityDpi = fullConfig.densityDpi; - // The smallest screen width is the short side of screen bounds. Because the bounds - // and density won't be changed, smallestScreenWidthDp is also fixed. - overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp; - if (ActivityInfo.isFixedOrientation(getOverrideOrientation())) { - // lock rotation too. When in size-compat, onConfigurationChanged will watch for and - // apply runtime rotation changes. - overrideConfig.windowConfiguration.setRotation( - fullConfig.windowConfiguration.getRotation()); - } - - final Rect letterboxedContainerBounds = mAppCompatController - .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds(); - - // The role of AppCompatDisplayInsets is like the override bounds. - mAppCompatDisplayInsets = - new AppCompatDisplayInsets( - mDisplayContent, this, letterboxedContainerBounds, - mResolveConfigHint.mUseOverrideInsetsForConfig); - } - - private void clearSizeCompatModeAttributes() { - mInSizeCompatModeForBounds = false; - final float lastSizeCompatScale = mSizeCompatScale; - mSizeCompatScale = 1f; - if (mSizeCompatScale != lastSizeCompatScale) { - forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); - } - mSizeCompatBounds = null; - mAppCompatDisplayInsets = null; - mAppCompatController.getTransparentPolicy().clearInheritedAppCompatDisplayInsets(); - } - - @VisibleForTesting - void clearSizeCompatMode() { - clearSizeCompatModeAttributes(); - // Clear config override in #updateAppCompatDisplayInsets(). - final int activityType = getActivityType(); - final Configuration overrideConfig = getRequestedOverrideConfiguration(); - overrideConfig.unset(); - // Keep the activity type which was set when attaching to a task to prevent leaving it - // undefined. - overrideConfig.windowConfiguration.setActivityType(activityType); - onRequestedOverrideConfigurationChanged(overrideConfig); + return mAppCompatController.getAppCompatSizeCompatModePolicy().hasSizeCompatBounds(); } @Override @@ -8401,7 +8303,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { - return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); + // We need to invoke {#getCompatScale()} only if the CompatScale is not available. + return mAppCompatController.getAppCompatSizeCompatModePolicy() + .getCompatScaleIfAvailable(ActivityRecord.super::getCompatScale); } @Override @@ -8469,8 +8373,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveAspectRatioRestriction(newParentConfiguration); } final AppCompatDisplayInsets appCompatDisplayInsets = getAppCompatDisplayInsets(); + final AppCompatSizeCompatModePolicy scmPolicy = + mAppCompatController.getAppCompatSizeCompatModePolicy(); if (appCompatDisplayInsets != null) { - resolveSizeCompatModeConfiguration(newParentConfiguration, appCompatDisplayInsets); + scmPolicy.resolveSizeCompatModeConfiguration(newParentConfiguration, + appCompatDisplayInsets, mTmpBounds); } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) { // We ignore activities' requested orientation in multi-window modes. They may be // taken into consideration in resolveFixedOrientationConfiguration call above. @@ -8488,13 +8395,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!Flags.immersiveAppRepositioning() && !mAppCompatController.getAppCompatAspectRatioPolicy() .isLetterboxedForFixedOrientationAndAspectRatio() - && !mInSizeCompatModeForBounds + && !scmPolicy.isInSizeCompatModeForBounds() && !mAppCompatController.getAppCompatAspectRatioOverrides() .hasFullscreenOverride()) { resolveAspectRatioRestriction(newParentConfiguration); } - if (isFixedOrientationLetterboxAllowed || mAppCompatDisplayInsets != null + if (isFixedOrientationLetterboxAllowed + || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance() // In fullscreen, can be letterboxed for aspect ratio. || !inMultiWindowMode()) { updateResolvedBoundsPosition(newParentConfiguration); @@ -8502,7 +8410,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean isIgnoreOrientationRequest = mDisplayContent != null && mDisplayContent.getIgnoreOrientationRequest(); - if (mAppCompatDisplayInsets == null + if (!scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance() // for size compat mode set in updateAppCompatDisplayInsets // Fixed orientation letterboxing is possible on both large screen devices // with ignoreOrientationRequest enabled and on phones in split screen even with @@ -8551,7 +8459,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A info.neverSandboxDisplayApis(sConstrainDisplayApisConfig), info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig), !matchParentBounds(), - mAppCompatDisplayInsets != null, + scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance(), shouldCreateAppCompatDisplayInsets()); } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); @@ -8575,7 +8483,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return Rect.copyOrNull(mResolveConfigHint.mParentAppBoundsOverride); } - private void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, + void computeConfigByResolveHint(@NonNull Configuration resolvedConfig, @NonNull Configuration parentConfig) { task.computeConfigResourceOverrides(resolvedConfig, parentConfig, mResolveConfigHint); // Reset the temp info which should only take effect for the specified computation. @@ -8627,7 +8535,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mAppCompatController.getTransparentPolicy().isRunning()) { return mAppCompatController.getTransparentPolicy().getInheritedAppCompatState(); } - if (mInSizeCompatModeForBounds) { + final AppCompatSizeCompatModePolicy scmPolicy = mAppCompatController + .getAppCompatSizeCompatModePolicy(); + if (scmPolicy.isInSizeCompatModeForBounds()) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE; } // Letterbox for fixed orientation. This check returns true only when an activity is @@ -8663,8 +8573,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (resolvedBounds.isEmpty()) { return; } - final Rect screenResolvedBounds = - mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; + final AppCompatSizeCompatModePolicy scmPolicy = + mAppCompatController.getAppCompatSizeCompatModePolicy(); + final Rect screenResolvedBounds = scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds); final Rect parentAppBounds = mResolveConfigHint.mParentAppBoundsOverride; final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final float screenResolvedBoundsWidth = screenResolvedBounds.width(); @@ -8720,14 +8631,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A - screenResolvedBounds.top + parentAppBounds.top); } } - - if (mSizeCompatBounds != null) { - mSizeCompatBounds.offset(offsetX , offsetY); - final int dy = mSizeCompatBounds.top - resolvedBounds.top; - final int dx = mSizeCompatBounds.left - resolvedBounds.left; - offsetBounds(resolvedConfig, dx, dy); - } else { - offsetBounds(resolvedConfig, offsetX, offsetY); + // If in SCM, apply offset to resolved bounds relative to size compat bounds. If + // not, apply directly to resolved bounds. + if (!scmPolicy.applyOffsetIfNeeded(resolvedBounds, resolvedConfig, offsetX, offsetY)) { + AppCompatUtils.offsetBounds(resolvedConfig, offsetX, offsetY); } // If the top is aligned with parentAppBounds add the vertical insets back so that the app @@ -8735,9 +8642,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top && !isImmersiveMode) { resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top; - if (mSizeCompatBounds != null) { - mSizeCompatBounds.top = parentBounds.top; - } + scmPolicy.alignToTopIfNeeded(parentBounds); } // Since bounds has changed, the configuration needs to be computed accordingly. @@ -8747,13 +8652,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // easier to resolve the relative position in parent container. However, if the activity is // scaled, the position should follow the scale because the configuration will be sent to // the client which is expected to be in a scaled environment. - if (mSizeCompatScale != 1f) { - final int screenPosX = resolvedBounds.left; - final int screenPosY = resolvedBounds.top; - final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX; - final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY; - offsetBounds(resolvedConfig, dx, dy); - } + scmPolicy.applySizeCompatScaleIfNeeded(resolvedBounds, resolvedConfig); } boolean isImmersiveMode(@NonNull Rect parentBounds) { @@ -8775,7 +8674,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @NonNull Rect getScreenResolvedBounds() { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); - return mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds; + final AppCompatSizeCompatModePolicy scmPolicy = + mAppCompatController.getAppCompatSizeCompatModePolicy(); + return scmPolicy.replaceResolvedBoundsIfNeeded(resolvedBounds); } void recomputeConfiguration() { @@ -8927,8 +8828,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return; } final AppCompatDisplayInsets mAppCompatDisplayInsets = getAppCompatDisplayInsets(); + final AppCompatSizeCompatModePolicy scmPolicy = + mAppCompatController.getAppCompatSizeCompatModePolicy(); - if (mAppCompatDisplayInsets != null + if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance() && !mAppCompatDisplayInsets.mIsInFixedOrientationOrAspectRatioLetterbox) { // App prefers to keep its original size. // If the size compat is from previous fixed orientation letterboxing, we may want to @@ -8981,7 +8884,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A .applyDesiredAspectRatio(newParentConfig, parentBounds, resolvedBounds, containingBoundsWithInsets, containingBounds); - if (mAppCompatDisplayInsets != null) { + if (scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) { mAppCompatDisplayInsets.getBoundsByRotation(mTmpBounds, newParentConfig.windowConfiguration.getRotation()); if (resolvedBounds.width() != mTmpBounds.width() @@ -9042,246 +8945,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } - /** - * Resolves consistent screen configuration for orientation and rotation changes without - * inheriting the parent bounds. - */ - private void resolveSizeCompatModeConfiguration(Configuration newParentConfiguration, - @NonNull AppCompatDisplayInsets appCompatDisplayInsets) { - 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 boolean useResolvedBounds = Flags.immersiveAppRepositioning() - ? mAppCompatController.getAppCompatAspectRatioPolicy() - .isAspectRatioApplied() - : mAppCompatController.getAppCompatAspectRatioPolicy() - .isLetterboxedForFixedOrientationAndAspectRatio(); - final Rect containerBounds = useResolvedBounds - ? new Rect(resolvedBounds) - : newParentConfiguration.windowConfiguration.getBounds(); - final Rect containerAppBounds = useResolvedBounds - ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) - : mResolveConfigHint.mParentAppBoundsOverride; - - final int requestedOrientation = getRequestedConfigurationOrientation(); - final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; - final int parentOrientation = mResolveConfigHint.mUseOverrideInsetsForConfig - ? mResolveConfigHint.mTmpOverrideConfigOrientation - : newParentConfiguration.orientation; - final int orientation = orientationRequested - ? requestedOrientation - // We should use the original orientation of the activity when possible to avoid - // forcing the activity in the opposite orientation. - : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED - ? appCompatDisplayInsets.mOriginalRequestedOrientation - : parentOrientation; - int rotation = newParentConfiguration.windowConfiguration.getRotation(); - final boolean isFixedToUserRotation = mDisplayContent == null - || mDisplayContent.getDisplayRotation().isFixedToUserRotation(); - if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) { - // Use parent rotation because the original display can be rotated. - resolvedConfig.windowConfiguration.setRotation(rotation); - } else { - final int overrideRotation = resolvedConfig.windowConfiguration.getRotation(); - if (overrideRotation != ROTATION_UNDEFINED) { - rotation = overrideRotation; - } - } - - // Use compat insets to lock width and height. We should not use the parent width and height - // because apps in compat mode should have a constant width and height. The compat insets - // are locked when the app is first launched and are never changed after that, so we can - // rely on them to contain the original and unchanging width and height of the app. - final Rect containingAppBounds = new Rect(); - final Rect containingBounds = mTmpBounds; - appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, - orientation, orientationRequested, isFixedToUserRotation); - resolvedBounds.set(containingBounds); - // The size of floating task is fixed (only swap), so the aspect ratio is already correct. - if (!appCompatDisplayInsets.mIsFloating) { - mAppCompatController.getAppCompatAspectRatioPolicy() - .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds, - containingBounds); - } - - // Use resolvedBounds to compute other override configurations such as appBounds. The bounds - // are calculated in compat container space. The actual position on screen will be applied - // later, so the calculation is simpler that doesn't need to involve offset from parent. - mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets; - computeConfigByResolveHint(resolvedConfig, newParentConfiguration); - // Use current screen layout as source because the size of app is independent to parent. - resolvedConfig.screenLayout = computeScreenLayout( - getConfiguration().screenLayout, resolvedConfig.screenWidthDp, - resolvedConfig.screenHeightDp); - - // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside - // the parent bounds appropriately. - if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) { - resolvedConfig.orientation = parentOrientation; - } - - // 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 container app bounds (parent bounds or fixed orientation bounds) - // "#" is the {@code resolvedBounds} that applies to application. - // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. - // ------------------------------ - // | | - // | @@@@*********@@@@### | - // | @ * * @ # | - // | @ * * @ # | - // | @ * * @ # | - // | @@@@*********@@@@ # | - // ---------#--------------#----- - // # # - // ################ - // 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 the size compatibility bounds into the region which is available - // to application. - final float lastSizeCompatScale = mSizeCompatScale; - updateSizeCompatScale(resolvedAppBounds, containerAppBounds); - - final int containerTopInset = containerAppBounds.top - containerBounds.top; - final boolean topNotAligned = - containerTopInset != resolvedAppBounds.top - resolvedBounds.top; - if (mSizeCompatScale != 1f || topNotAligned) { - if (mSizeCompatBounds == null) { - mSizeCompatBounds = new Rect(); - } - mSizeCompatBounds.set(resolvedAppBounds); - mSizeCompatBounds.offsetTo(0, 0); - mSizeCompatBounds.scale(mSizeCompatScale); - // The insets are included in height, e.g. the area of real cutout shouldn't be scaled. - mSizeCompatBounds.bottom += containerTopInset; - } else { - mSizeCompatBounds = null; - } - if (mSizeCompatScale != lastSizeCompatScale) { - forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */); - } - - // The position will be later adjusted in updateResolvedBoundsPosition. - // Above coordinates are in "@" space, now place "*" and "#" to screen space. - final boolean fillContainer = resolvedBounds.equals(containingBounds); - final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; - final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; - - if (screenPosX != 0 || screenPosY != 0) { - if (mSizeCompatBounds != null) { - mSizeCompatBounds.offset(screenPosX, screenPosY); - } - // Add the global coordinates and remove the local coordinates. - final int dx = screenPosX - resolvedBounds.left; - final int dy = screenPosY - resolvedBounds.top; - offsetBounds(resolvedConfig, dx, dy); - } - - mInSizeCompatModeForBounds = - isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); - } - - void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { - mSizeCompatScale = mAppCompatController.getTransparentPolicy() - .findOpaqueNotFinishingActivityBelow() - .map(activityRecord -> activityRecord.mSizeCompatScale) - .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds)); - } - - private float calculateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { - final int contentW = resolvedAppBounds.width(); - final int contentH = resolvedAppBounds.height(); - final int viewportW = containerAppBounds.width(); - final int viewportH = containerAppBounds.height(); - // Allow an application to be up-scaled if its window is smaller than its - // original container or if it's a freeform window in desktop mode. - boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH) - || (canEnterDesktopMode(mAtmService.mContext) - && getWindowingMode() == WINDOWING_MODE_FREEFORM); - return shouldAllowUpscaling ? Math.min( - (float) viewportW / contentW, (float) viewportH / contentH) : 1f; - } - - private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { - if (mAppCompatController.getTransparentPolicy().isRunning()) { - // 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(); - 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. - final float maxAspectRatio = getMaxAspectRatio(); - if (maxAspectRatio > 0) { - final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) - / Math.min(appWidth, appHeight); - if (aspectRatio >= maxAspectRatio) { - // The current size has reached the max aspect ratio. - return false; - } - } - final float minAspectRatio = getMinAspectRatio(); - if (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 <= minAspectRatio) { - // The long side has reached the parent. - return false; - } - } - return true; - } - - /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/ - private static int getCenterOffset(int viewportDim, int contentDim) { - return (int) ((viewportDim - contentDim + 1) * 0.5f); - } - - private static void offsetBounds(Configuration inOutConfig, int offsetX, int offsetY) { - inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY); - inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY); - } - @Override public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities final Rect superBounds = super.getBounds(); + final AppCompatSizeCompatModePolicy scmPolicy = + mAppCompatController.getAppCompatSizeCompatModePolicy(); return mAppCompatController.getTransparentPolicy().findOpaqueNotFinishingActivityBelow() .map(ActivityRecord::getBounds) - .orElseGet(() -> { - if (mSizeCompatBounds != null) { - return mSizeCompatBounds; - } - return superBounds; - }); + .orElseGet(() -> scmPolicy.getAppSizeCompatBoundsIfAvailable(superBounds)); } @Override @@ -9612,7 +9284,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mVisibleRequested) { // Calling from here rather than resolveOverrideConfiguration to ensure that this is // called after full config is updated in ConfigurationContainer#onConfigurationChanged. - updateAppCompatDisplayInsets(); + mAppCompatController.getAppCompatSizeCompatModePolicy().updateAppCompatDisplayInsets(); } // Short circuit: if the two full configurations are equal (the common case), then there is @@ -9952,7 +9624,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Reset the existing override configuration so it can be updated according to the latest // configuration. - clearSizeCompatMode(); + mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); if (!attachedToProcess()) { return; diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java index 3c3b77374d70..173362c16728 100644 --- a/services/core/java/com/android/server/wm/AppCompatController.java +++ b/services/core/java/com/android/server/wm/AppCompatController.java @@ -46,6 +46,8 @@ class AppCompatController { private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery; @NonNull private final AppCompatLetterboxPolicy mAppCompatLetterboxPolicy; + @NonNull + private final AppCompatSizeCompatModePolicy mAppCompatSizeCompatModePolicy; AppCompatController(@NonNull WindowManagerService wmService, @NonNull ActivityRecord activityRecord) { @@ -67,6 +69,8 @@ class AppCompatController { wmService.mAppCompatConfiguration); mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord, mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration); + mAppCompatSizeCompatModePolicy = new AppCompatSizeCompatModePolicy(mActivityRecord, + mAppCompatOverrides); } @NonNull @@ -152,9 +156,15 @@ class AppCompatController { return mAppCompatOverrides.getAppCompatLetterboxOverrides(); } + @NonNull + AppCompatSizeCompatModePolicy getAppCompatSizeCompatModePolicy() { + return mAppCompatSizeCompatModePolicy; + } + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { getTransparentPolicy().dump(pw, prefix); getAppCompatLetterboxPolicy().dump(pw, prefix); + getAppCompatSizeCompatModePolicy().dump(pw, prefix); } } diff --git a/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java new file mode 100644 index 000000000000..3be266e2951b --- /dev/null +++ b/services/core/java/com/android/server/wm/AppCompatSizeCompatModePolicy.java @@ -0,0 +1,439 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; +import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.content.res.Configuration.ORIENTATION_UNDEFINED; + +import static com.android.server.wm.DesktopModeHelper.canEnterDesktopMode; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.Rect; + +import com.android.window.flags.Flags; + +import java.io.PrintWriter; +import java.util.function.DoubleSupplier; + +/** + * Encapsulate logic related to the SizeCompatMode. + */ +class AppCompatSizeCompatModePolicy { + + @NonNull + private final ActivityRecord mActivityRecord; + @NonNull + private final AppCompatOverrides mAppCompatOverrides; + + // Whether this activity is in size compatibility mode because its bounds don't fit in parent + // naturally. + private boolean mInSizeCompatModeForBounds = false; + /** + * The scale to fit at least one side of the activity to its parent. If the activity uses + * 1920x1080, and the actually size on the screen is 960x540, then the scale is 0.5. + */ + private float mSizeCompatScale = 1f; + + /** + * The bounds in global coordinates for activity in size compatibility mode. + * @see #hasSizeCompatBounds() + */ + private Rect mSizeCompatBounds; + + /** + * The precomputed display insets for resolving configuration. It will be non-null if + * {@link #shouldCreateAppCompatDisplayInsets} returns {@code true}. + */ + @Nullable + private AppCompatDisplayInsets mAppCompatDisplayInsets; + + AppCompatSizeCompatModePolicy(@NonNull ActivityRecord activityRecord, + @NonNull AppCompatOverrides appCompatOverrides) { + mActivityRecord = activityRecord; + mAppCompatOverrides = appCompatOverrides; + } + + boolean isInSizeCompatModeForBounds() { + return mInSizeCompatModeForBounds; + } + + void setInSizeCompatModeForBounds(boolean inSizeCompatModeForBounds) { + mInSizeCompatModeForBounds = inSizeCompatModeForBounds; + } + + boolean hasSizeCompatBounds() { + return mSizeCompatBounds != null; + } + + /** + * @return The {@code true} if the current instance has {@link mAppCompatDisplayInsets} without + * considering the inheritance implemented in {@link #getAppCompatDisplayInsets()} + */ + boolean hasAppCompatDisplayInsetsWithoutInheritance() { + return mAppCompatDisplayInsets != null; + } + + @Nullable + AppCompatDisplayInsets getAppCompatDisplayInsets() { + final TransparentPolicy transparentPolicy = mActivityRecord.mAppCompatController + .getTransparentPolicy(); + if (transparentPolicy.isRunning()) { + return transparentPolicy.getInheritedAppCompatDisplayInsets(); + } + return mAppCompatDisplayInsets; + } + + float getCompatScaleIfAvailable(@NonNull DoubleSupplier scaleWhenNotAvailable) { + return hasSizeCompatBounds() ? mSizeCompatScale + : (float) scaleWhenNotAvailable.getAsDouble(); + } + + @NonNull + Rect getAppSizeCompatBoundsIfAvailable(@NonNull Rect boundsWhenNotAvailable) { + return hasSizeCompatBounds() ? mSizeCompatBounds : boundsWhenNotAvailable; + } + + @NonNull + Rect replaceResolvedBoundsIfNeeded(@NonNull Rect resolvedBounds) { + return hasSizeCompatBounds() ? mSizeCompatBounds : resolvedBounds; + } + + boolean applyOffsetIfNeeded(@NonNull Rect resolvedBounds, + @NonNull Configuration resolvedConfig, int offsetX, int offsetY) { + if (hasSizeCompatBounds()) { + mSizeCompatBounds.offset(offsetX , offsetY); + final int dy = mSizeCompatBounds.top - resolvedBounds.top; + final int dx = mSizeCompatBounds.left - resolvedBounds.left; + AppCompatUtils.offsetBounds(resolvedConfig, dx, dy); + return true; + } + return false; + } + + void alignToTopIfNeeded(@NonNull Rect parentBounds) { + if (hasSizeCompatBounds()) { + mSizeCompatBounds.top = parentBounds.top; + } + } + + void applySizeCompatScaleIfNeeded(@NonNull Rect resolvedBounds, + @NonNull Configuration resolvedConfig) { + if (mSizeCompatScale != 1f) { + final int screenPosX = resolvedBounds.left; + final int screenPosY = resolvedBounds.top; + final int dx = (int) (screenPosX / mSizeCompatScale + 0.5f) - screenPosX; + final int dy = (int) (screenPosY / mSizeCompatScale + 0.5f) - screenPosY; + AppCompatUtils.offsetBounds(resolvedConfig, dx, dy); + } + } + + void updateSizeCompatScale(@NonNull Rect resolvedAppBounds, @NonNull Rect containerAppBounds) { + mSizeCompatScale = mActivityRecord.mAppCompatController.getTransparentPolicy() + .findOpaqueNotFinishingActivityBelow() + .map(activityRecord -> mSizeCompatScale) + .orElseGet(() -> calculateSizeCompatScale(resolvedAppBounds, containerAppBounds)); + } + + void clearSizeCompatModeAttributes() { + mInSizeCompatModeForBounds = false; + final float lastSizeCompatScale = mSizeCompatScale; + mSizeCompatScale = 1f; + if (mSizeCompatScale != lastSizeCompatScale) { + mActivityRecord.forAllWindows(WindowState::updateGlobalScale, + false /* traverseTopToBottom */); + } + mSizeCompatBounds = null; + mAppCompatDisplayInsets = null; + mActivityRecord.mAppCompatController.getTransparentPolicy() + .clearInheritedAppCompatDisplayInsets(); + } + + void clearSizeCompatMode() { + clearSizeCompatModeAttributes(); + // Clear config override in #updateAppCompatDisplayInsets(). + final int activityType = mActivityRecord.getActivityType(); + final Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration(); + overrideConfig.unset(); + // Keep the activity type which was set when attaching to a task to prevent leaving it + // undefined. + overrideConfig.windowConfiguration.setActivityType(activityType); + mActivityRecord.onRequestedOverrideConfigurationChanged(overrideConfig); + } + + void dump(@NonNull PrintWriter pw, @NonNull String prefix) { + if (mSizeCompatScale != 1f || hasSizeCompatBounds()) { + pw.println(prefix + "mSizeCompatScale=" + mSizeCompatScale + " mSizeCompatBounds=" + + mSizeCompatBounds); + } + } + + /** + * Resolves consistent screen configuration for orientation and rotation changes without + * inheriting the parent bounds. + */ + void resolveSizeCompatModeConfiguration(@NonNull Configuration newParentConfiguration, + @NonNull AppCompatDisplayInsets appCompatDisplayInsets, @NonNull Rect tmpBounds) { + final Configuration resolvedConfig = mActivityRecord.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 AppCompatAspectRatioPolicy aspectRatioPolicy = mActivityRecord.mAppCompatController + .getAppCompatAspectRatioPolicy(); + final boolean useResolvedBounds = Flags.immersiveAppRepositioning() + ? aspectRatioPolicy.isAspectRatioApplied() + : aspectRatioPolicy.isLetterboxedForFixedOrientationAndAspectRatio(); + final Rect containerBounds = useResolvedBounds + ? new Rect(resolvedBounds) + : newParentConfiguration.windowConfiguration.getBounds(); + final Rect containerAppBounds = useResolvedBounds + ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) + : mActivityRecord.mResolveConfigHint.mParentAppBoundsOverride; + + final int requestedOrientation = mActivityRecord.getRequestedConfigurationOrientation(); + final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED; + final int parentOrientation = mActivityRecord.mResolveConfigHint.mUseOverrideInsetsForConfig + ? mActivityRecord.mResolveConfigHint.mTmpOverrideConfigOrientation + : newParentConfiguration.orientation; + final int orientation = orientationRequested + ? requestedOrientation + // We should use the original orientation of the activity when possible to avoid + // forcing the activity in the opposite orientation. + : appCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED + ? appCompatDisplayInsets.mOriginalRequestedOrientation + : parentOrientation; + int rotation = newParentConfiguration.windowConfiguration.getRotation(); + final boolean isFixedToUserRotation = mActivityRecord.mDisplayContent == null + || mActivityRecord.mDisplayContent.getDisplayRotation().isFixedToUserRotation(); + if (!isFixedToUserRotation && !appCompatDisplayInsets.mIsFloating) { + // Use parent rotation because the original display can be rotated. + resolvedConfig.windowConfiguration.setRotation(rotation); + } else { + final int overrideRotation = resolvedConfig.windowConfiguration.getRotation(); + if (overrideRotation != ROTATION_UNDEFINED) { + rotation = overrideRotation; + } + } + + // Use compat insets to lock width and height. We should not use the parent width and height + // because apps in compat mode should have a constant width and height. The compat insets + // are locked when the app is first launched and are never changed after that, so we can + // rely on them to contain the original and unchanging width and height of the app. + final Rect containingAppBounds = new Rect(); + final Rect containingBounds = tmpBounds; + appCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, + orientation, orientationRequested, isFixedToUserRotation); + resolvedBounds.set(containingBounds); + // The size of floating task is fixed (only swap), so the aspect ratio is already correct. + if (!appCompatDisplayInsets.mIsFloating) { + mActivityRecord.mAppCompatController.getAppCompatAspectRatioPolicy() + .applyAspectRatioForLetterbox(resolvedBounds, containingAppBounds, + containingBounds); + } + + // Use resolvedBounds to compute other override configurations such as appBounds. The bounds + // are calculated in compat container space. The actual position on screen will be applied + // later, so the calculation is simpler that doesn't need to involve offset from parent. + mActivityRecord.mResolveConfigHint.mTmpCompatInsets = appCompatDisplayInsets; + mActivityRecord.computeConfigByResolveHint(resolvedConfig, newParentConfiguration); + // Use current screen layout as source because the size of app is independent to parent. + resolvedConfig.screenLayout = ActivityRecord.computeScreenLayout( + mActivityRecord.getConfiguration().screenLayout, resolvedConfig.screenWidthDp, + resolvedConfig.screenHeightDp); + + // Use parent orientation if it cannot be decided by bounds, so the activity can fit inside + // the parent bounds appropriately. + if (resolvedConfig.screenWidthDp == resolvedConfig.screenHeightDp) { + resolvedConfig.orientation = parentOrientation; + } + + // 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 container app bounds (parent bounds or fixed orientation bounds) + // "#" is the {@code resolvedBounds} that applies to application. + // "*" is the {@code mSizeCompatBounds} that used to show on screen if scaled. + // ------------------------------ + // | | + // | @@@@*********@@@@### | + // | @ * * @ # | + // | @ * * @ # | + // | @ * * @ # | + // | @@@@*********@@@@ # | + // ---------#--------------#----- + // # # + // ################ + // 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 the size compatibility bounds into the region which is available + // to application. + final float lastSizeCompatScale = mSizeCompatScale; + updateSizeCompatScale(resolvedAppBounds, containerAppBounds); + + final int containerTopInset = containerAppBounds.top - containerBounds.top; + final boolean topNotAligned = + containerTopInset != resolvedAppBounds.top - resolvedBounds.top; + if (mSizeCompatScale != 1f || topNotAligned) { + if (mSizeCompatBounds == null) { + mSizeCompatBounds = new Rect(); + } + mSizeCompatBounds.set(resolvedAppBounds); + mSizeCompatBounds.offsetTo(0, 0); + mSizeCompatBounds.scale(mSizeCompatScale); + // The insets are included in height, e.g. the area of real cutout shouldn't be scaled. + mSizeCompatBounds.bottom += containerTopInset; + } else { + mSizeCompatBounds = null; + } + if (mSizeCompatScale != lastSizeCompatScale) { + mActivityRecord.forAllWindows(WindowState::updateGlobalScale, + false /* traverseTopToBottom */); + } + + // The position will be later adjusted in updateResolvedBoundsPosition. + // Above coordinates are in "@" space, now place "*" and "#" to screen space. + final boolean fillContainer = resolvedBounds.equals(containingBounds); + final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left; + final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top; + + if (screenPosX != 0 || screenPosY != 0) { + if (hasSizeCompatBounds()) { + mSizeCompatBounds.offset(screenPosX, screenPosY); + } + // Add the global coordinates and remove the local coordinates. + final int dx = screenPosX - resolvedBounds.left; + final int dy = screenPosY - resolvedBounds.top; + AppCompatUtils.offsetBounds(resolvedConfig, dx, dy); + } + + mInSizeCompatModeForBounds = isInSizeCompatModeForBounds(resolvedAppBounds, + containerAppBounds); + } + + // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. + void updateAppCompatDisplayInsets() { + if (getAppCompatDisplayInsets() != null + || !mActivityRecord.shouldCreateAppCompatDisplayInsets()) { + // The override configuration is set only once in size compatibility mode. + return; + } + + Configuration overrideConfig = mActivityRecord.getRequestedOverrideConfiguration(); + final Configuration fullConfig = mActivityRecord.getConfiguration(); + + // Ensure the screen related fields are set. It is used to prevent activity relaunch + // when moving between displays. For screenWidthDp and screenWidthDp, because they + // are relative to bounds and density, they will be calculated in + // {@link Task#computeConfigResourceOverrides} and the result will also be + // relatively fixed. + overrideConfig.colorMode = fullConfig.colorMode; + overrideConfig.densityDpi = fullConfig.densityDpi; + // The smallest screen width is the short side of screen bounds. Because the bounds + // and density won't be changed, smallestScreenWidthDp is also fixed. + overrideConfig.smallestScreenWidthDp = fullConfig.smallestScreenWidthDp; + if (ActivityInfo.isFixedOrientation(mActivityRecord.getOverrideOrientation())) { + // lock rotation too. When in size-compat, onConfigurationChanged will watch for and + // apply runtime rotation changes. + overrideConfig.windowConfiguration.setRotation( + fullConfig.windowConfiguration.getRotation()); + } + + final Rect letterboxedContainerBounds = mActivityRecord.mAppCompatController + .getAppCompatAspectRatioPolicy().getLetterboxedContainerBounds(); + + // The role of AppCompatDisplayInsets is like the override bounds. + mAppCompatDisplayInsets = + new AppCompatDisplayInsets(mActivityRecord.mDisplayContent, mActivityRecord, + letterboxedContainerBounds, mActivityRecord.mResolveConfigHint + .mUseOverrideInsetsForConfig); + } + + + private boolean isInSizeCompatModeForBounds(final @NonNull Rect appBounds, + final @NonNull Rect containerBounds) { + if (mActivityRecord.mAppCompatController.getTransparentPolicy().isRunning()) { + // 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(); + 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. + final float maxAspectRatio = mActivityRecord.getMaxAspectRatio(); + if (maxAspectRatio > 0) { + final float aspectRatio = (0.5f + Math.max(appWidth, appHeight)) + / Math.min(appWidth, appHeight); + if (aspectRatio >= maxAspectRatio) { + // The current size has reached the max aspect ratio. + return false; + } + } + final float minAspectRatio = mActivityRecord.getMinAspectRatio(); + if (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 <= minAspectRatio) { + // The long side has reached the parent. + return false; + } + } + return true; + } + + private float calculateSizeCompatScale(@NonNull Rect resolvedAppBounds, + @NonNull Rect containerAppBounds) { + final int contentW = resolvedAppBounds.width(); + final int contentH = resolvedAppBounds.height(); + final int viewportW = containerAppBounds.width(); + final int viewportH = containerAppBounds.height(); + // Allow an application to be up-scaled if its window is smaller than its + // original container or if it's a freeform window in desktop mode. + boolean shouldAllowUpscaling = !(contentW <= viewportW && contentH <= viewportH) + || (canEnterDesktopMode(mActivityRecord.mAtmService.mContext) + && mActivityRecord.getWindowingMode() == WINDOWING_MODE_FREEFORM); + return shouldAllowUpscaling ? Math.min( + (float) viewportW / contentW, (float) viewportH / contentH) : 1f; + } +} diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java index e3ff85171c0e..69421d0d5c96 100644 --- a/services/core/java/com/android/server/wm/AppCompatUtils.java +++ b/services/core/java/com/android/server/wm/AppCompatUtils.java @@ -251,6 +251,11 @@ final class AppCompatUtils { } } + static void offsetBounds(@NonNull Configuration inOutConfig, int offsetX, int offsetY) { + inOutConfig.windowConfiguration.getBounds().offset(offsetX, offsetY); + inOutConfig.windowConfiguration.getAppBounds().offset(offsetX, offsetY); + } + private static void clearAppCompatTaskInfo(@NonNull AppCompatTaskInfo info) { info.topActivityLetterboxVerticalPosition = TaskInfo.PROPERTY_VALUE_UNSET; info.topActivityLetterboxHorizontalPosition = TaskInfo.PROPERTY_VALUE_UNSET; diff --git a/services/core/java/com/android/server/wm/TransparentPolicy.java b/services/core/java/com/android/server/wm/TransparentPolicy.java index 016b65ed06d8..f1941afe8f58 100644 --- a/services/core/java/com/android/server/wm/TransparentPolicy.java +++ b/services/core/java/com/android/server/wm/TransparentPolicy.java @@ -201,8 +201,10 @@ class TransparentPolicy { // never has letterbox. return true; } + final AppCompatSizeCompatModePolicy scmPolicy = mActivityRecord.mAppCompatController + .getAppCompatSizeCompatModePolicy(); if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() - || mActivityRecord.hasAppCompatDisplayInsetsWithoutInheritance()) { + || scmPolicy.hasAppCompatDisplayInsetsWithoutInheritance()) { return true; } return false; 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 1423811da0c1..0d8b7208bcac 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -705,7 +705,7 @@ public class ActivityRecordTests extends WindowTestsBase { assertEquals(ORIENTATION_PORTRAIT, activity.getConfiguration().orientation); // Clear size compat. - activity.clearSizeCompatMode(); + activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); activity.ensureActivityConfiguration(); mDisplayContent.sendNewConfiguration(); diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java index d7cef599358a..a7a08b25fba2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java +++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java @@ -499,7 +499,7 @@ class AppCompatActivityRobot { activity.setRequestedOrientation(screenOrientation); } // Make sure to use the provided configuration to construct the size compat fields. - activity.clearSizeCompatMode(); + activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); activity.ensureActivityConfiguration(); // Make sure the display configuration reflects the change of activity. if (activity.mDisplayContent.updateOrientation()) { 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 5bb437827dae..f74340113a04 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -613,7 +613,7 @@ public class SizeCompatTests extends WindowTestsBase { assertFalse(mActivity.mDisplayContent.shouldImeAttachedToApp()); // Recompute the natural configuration without resolving size compat configuration. - mActivity.clearSizeCompatMode(); + mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); mActivity.onConfigurationChanged(mTask.getConfiguration()); // It should keep non-attachable because the resolved bounds will be computed according to // the aspect ratio that won't match its parent bounds. @@ -706,7 +706,7 @@ public class SizeCompatTests extends WindowTestsBase { / originalBounds.width())); // Recompute the natural configuration in the new display. - mActivity.clearSizeCompatMode(); + mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); mActivity.ensureActivityConfiguration(); // Because the display cannot rotate, the portrait activity will fit the short side of // display with keeping portrait bounds [200, 0 - 700, 1000] in center. @@ -1482,7 +1482,7 @@ public class SizeCompatTests extends WindowTestsBase { // After changing the orientation to portrait the override should be applied. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - activity.clearSizeCompatMode(); + activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1200, activity.getBounds().height()); @@ -1511,7 +1511,7 @@ public class SizeCompatTests extends WindowTestsBase { // After changing the orientation to portrait the override should be applied. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); - activity.clearSizeCompatMode(); + activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1200, activity.getBounds().height()); @@ -1538,7 +1538,7 @@ public class SizeCompatTests extends WindowTestsBase { // After changing the orientation to landscape the override shouldn't be applied. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); - activity.clearSizeCompatMode(); + activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); // The per-package override should have no effect assertEquals(1200, activity.getBounds().height()); @@ -3054,7 +3054,10 @@ public class SizeCompatTests extends WindowTestsBase { false /* deferPause */); // App still in size compat, and the bounds don't change. - verify(mActivity, never()).clearSizeCompatMode(); + final AppCompatSizeCompatModePolicy scmPolicy = mActivity.mAppCompatController + .getAppCompatSizeCompatModePolicy(); + spyOn(scmPolicy); + verify(scmPolicy, never()).clearSizeCompatMode(); assertFalse(mActivity.mAppCompatController.getAppCompatAspectRatioPolicy() .isLetterboxedForFixedOrientationAndAspectRatio()); assertDownScaled(); @@ -3896,7 +3899,7 @@ public class SizeCompatTests extends WindowTestsBase { private void recomputeNaturalConfigurationOfUnresizableActivity() { // Recompute the natural configuration of the non-resizable activity and the split screen. - mActivity.clearSizeCompatMode(); + mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); // Draw letterbox. mActivity.setVisible(false); @@ -4827,7 +4830,7 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(origDensity, mActivity.getConfiguration().densityDpi); // Activity should exit size compat with new density. - mActivity.clearSizeCompatMode(); + mActivity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); assertFitted(); assertEquals(newDensity, mActivity.getConfiguration().densityDpi); @@ -5013,7 +5016,7 @@ public class SizeCompatTests extends WindowTestsBase { activity.setRequestedOrientation(screenOrientation); } // Make sure to use the provided configuration to construct the size compat fields. - activity.clearSizeCompatMode(); + activity.mAppCompatController.getAppCompatSizeCompatModePolicy().clearSizeCompatMode(); activity.ensureActivityConfiguration(); // Make sure the display configuration reflects the change of activity. if (activity.mDisplayContent.updateOrientation()) { diff --git a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java index fa28d117c5bc..7a440e676b39 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/TransparentPolicyTest.java @@ -323,7 +323,10 @@ public class TransparentPolicyTest extends WindowTestsBase { ta.launchTransparentActivityInTask(); a.assertNotNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets); - a.applyToTopActivity(ActivityRecord::clearSizeCompatMode); + a.applyToTopActivity((top) -> { + top.mAppCompatController.getAppCompatSizeCompatModePolicy() + .clearSizeCompatMode(); + }); a.assertNullOnTopActivity(ActivityRecord::getAppCompatDisplayInsets); }); }); |