Loading services/core/java/com/android/server/wm/ActivityRecord.java +56 −38 Original line number Diff line number Diff line Loading @@ -7879,6 +7879,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mCompatDisplayInsets; } /** * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without * considering the inheritance implemented in {@link #getCompatDisplayInsets()} */ boolean hasCompatDisplayInsetsWithoutInheritance() { return mCompatDisplayInsets != null; } /** * @return {@code true} if this activity is in size compatibility mode that uses the different * density than its parent or its bounds don't fit in parent naturally. Loading @@ -7887,7 +7895,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mInSizeCompatModeForBounds) { return true; } if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets() if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets() // The orientation is different from parent when transforming. || isFixedRotationTransforming()) { return false; Loading Loading @@ -7958,11 +7966,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets(); return; } if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; } Loading Loading @@ -8025,9 +8029,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedSizeCompatScale(); } return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); } Loading Loading @@ -8071,7 +8072,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveFixedOrientationConfiguration(newParentConfiguration); } if (mCompatDisplayInsets != null) { if (getCompatDisplayInsets() != null) { resolveSizeCompatModeConfiguration(newParentConfiguration); } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) { // We ignore activities' requested orientation in multi-window modes. They may be Loading @@ -8089,7 +8090,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveAspectRatioRestriction(newParentConfiguration); } if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null // In fullscreen, can be letterboxed for aspect ratio. || !inMultiWindowMode()) { updateResolvedBoundsPosition(newParentConfiguration); Loading @@ -8097,7 +8098,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean isIgnoreOrientationRequest = mDisplayContent != null && mDisplayContent.getIgnoreOrientationRequest(); if (mCompatDisplayInsets == null // for size compat mode set in updateCompatDisplayInsets if (getCompatDisplayInsets() == null // for size compat mode set in updateCompatDisplayInsets // Fixed orientation letterboxing is possible on both large screen devices // with ignoreOrientationRequest enabled and on phones in split screen even with // ignoreOrientationRequest disabled. Loading Loading @@ -8143,7 +8145,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A info.neverSandboxDisplayApis(sConstrainDisplayApisConfig), info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig), !matchParentBounds(), mCompatDisplayInsets != null, getCompatDisplayInsets() != null, shouldCreateCompatDisplayInsets()); } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); Loading Loading @@ -8442,8 +8444,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || orientationRespectedWithInsets)) { return; } final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets(); if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) { if (compatDisplayInsets != null && !compatDisplayInsets.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 Loading Loading @@ -8498,9 +8501,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets, containingBounds, desiredAspectRatio); if (mCompatDisplayInsets != null) { mCompatDisplayInsets.getBoundsByRotation( mTmpBounds, newParentConfig.windowConfiguration.getRotation()); if (compatDisplayInsets != null) { compatDisplayInsets.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 Loading @@ -8514,7 +8517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig, mCompatDisplayInsets); newParentConfig, compatDisplayInsets); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } Loading Loading @@ -8571,13 +8574,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ? requestedOrientation // We should use the original orientation of the activity when possible to avoid // forcing the activity in the opposite orientation. : mCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED ? mCompatDisplayInsets.mOriginalRequestedOrientation : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED ? getCompatDisplayInsets().mOriginalRequestedOrientation : newParentConfiguration.orientation; int rotation = newParentConfiguration.windowConfiguration.getRotation(); final boolean isFixedToUserRotation = mDisplayContent == null || mDisplayContent.getDisplayRotation().isFixedToUserRotation(); if (!isFixedToUserRotation && !mCompatDisplayInsets.mIsFloating) { if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) { // Use parent rotation because the original display can be rotated. resolvedConfig.windowConfiguration.setRotation(rotation); } else { Loading @@ -8593,11 +8596,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // rely on them to contain the original and unchanging width and height of the app. final Rect containingAppBounds = new Rect(); final Rect containingBounds = mTmpBounds; mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, getCompatDisplayInsets().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 (!mCompatDisplayInsets.mIsFloating) { if (!getCompatDisplayInsets().mIsFloating) { mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds); } Loading @@ -8606,7 +8609,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, mCompatDisplayInsets); getCompatDisplayInsets()); // Use current screen layout as source because the size of app is independent to parent. resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, Loading Loading @@ -8641,14 +8644,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculates the scale the size compatibility bounds into the region which is available // to application. final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); final int viewportH = containerAppBounds.height(); final float lastSizeCompatScale = mSizeCompatScale; // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); updateSizeCompatScale(resolvedAppBounds, containerAppBounds); final int containerTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = containerTopInset != resolvedAppBounds.top - resolvedBounds.top; Loading Loading @@ -8688,6 +8686,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); } void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { // Only allow to scale down. mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow() .map(activityRecord -> activityRecord.mSizeCompatScale) .orElseGet(() -> { final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); final int viewportH = containerAppBounds.height(); return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min( (float) viewportW / contentW, (float) viewportH / contentH); }); } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity Loading Loading @@ -8750,10 +8762,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities final Rect superBounds = super.getBounds(); return mLetterboxUiController.findOpaqueNotFinishingActivityBelow() .map(ActivityRecord::getBounds) .orElseGet(() -> { if (mSizeCompatBounds != null) { return mSizeCompatBounds; } return super.getBounds(); return superBounds; }); } @Override Loading @@ -8778,7 +8796,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it // will keep the same bounds and screen configuration when it was first launched regardless // how its parent window changes, so that the sandbox API will provide a consistent result. if (mCompatDisplayInsets != null || shouldCreateCompatDisplayInsets()) { if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) { return true; } Loading Loading @@ -8820,7 +8838,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTransitionController.collect(this); } } if (mCompatDisplayInsets != null) { if (getCompatDisplayInsets() != null) { Configuration overrideConfig = getRequestedOverrideConfiguration(); // Adapt to changes in orientation locking. The app is still non-resizable, but // it can change which orientation is fixed. If the fixed orientation changes, Loading Loading @@ -8896,7 +8914,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mVisibleRequested) { // It may toggle the UI for user to restart the size compatibility mode activity. display.handleActivitySizeCompatModeIfNeeded(this); } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard && (app == null || !app.hasVisibleActivities())) { // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during // displays change. Displays are turned off during the change so mVisibleRequested Loading services/core/java/com/android/server/wm/LetterboxUiController.java +20 −20 Original line number Diff line number Diff line Loading @@ -193,12 +193,6 @@ final class LetterboxUiController { // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. private boolean mIsInheritedInSizeCompatMode; // This is the SizeCompatScale of the opaque activity beneath a translucent one private float mInheritedSizeCompatScale; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; Loading Loading @@ -735,8 +729,21 @@ final class LetterboxUiController { : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTask().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct // information and in particular it might provide a value for a smaller area making // the letterbox overlap with the translucent activity's frame. // If we use WindowState#getFrame() for the translucent activity's letterbox inner // frame, the letterbox will then be overlapped with the translucent activity's frame. // Because the surface layer of letterbox is lower than an activity window, this // won't crop the content, but it may affect other features that rely on values stored // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final Rect innerFrame = hasInheritedLetterboxBehavior() ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); Loading Loading @@ -1386,10 +1393,10 @@ final class LetterboxUiController { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null || mActivityRecord.fillsParent()) { if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return; } final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( Loading Loading @@ -1417,6 +1424,7 @@ final class LetterboxUiController { // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); inheritConfiguration(firstOpaqueActivityBeneath); return mutatedConfiguration; }); } Loading Loading @@ -1457,16 +1465,12 @@ final class LetterboxUiController { return mInheritedAppCompatState; } float getInheritedSizeCompatScale() { return mInheritedSizeCompatScale; } @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; } public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } Loading @@ -1486,7 +1490,7 @@ final class LetterboxUiController { * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } Loading @@ -1508,8 +1512,6 @@ final class LetterboxUiController { } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); mInheritedSizeCompatScale = firstOpaque.getCompatScale(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); } Loading @@ -1519,8 +1521,6 @@ final class LetterboxUiController { mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mIsInheritedInSizeCompatMode = false; mInheritedSizeCompatScale = 1f; mInheritedCompatDisplayInsets = null; } } services/core/java/com/android/server/wm/Task.java +5 −0 Original line number Diff line number Diff line Loading @@ -3455,6 +3455,11 @@ class Task extends TaskFragment { && top.getOrganizedTask() == this && top.isState(RESUMED); // Whether the direct top activity is in size compat mode on foreground. info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode(); if (info.topActivityInSizeCompat && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { // We hide the restart button in case of transparent activities. info.topActivityInSizeCompat = top.fillsParent(); } // Whether the direct top activity is eligible for letterbox education. info.topActivityEligibleForLetterboxEducation = isTopActivityResumed && top.isEligibleForLetterboxEducation(); Loading services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +9 −3 Original line number Diff line number Diff line Loading @@ -274,7 +274,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testTranslucentActivitiesWhenUnfolding() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mActivity.mDisplayContent.setIgnoreOrientationRequest( true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); Loading @@ -285,18 +286,23 @@ public class SizeCompatTests extends WindowTestsBase { .build(); doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(mActivity); // Halffold setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */); setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */); verify(mActivity).recomputeConfiguration(); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); clearInvocations(mActivity); // Unfold setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */); setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */); verify(mActivity).recomputeConfiguration(); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); } @Test Loading Loading
services/core/java/com/android/server/wm/ActivityRecord.java +56 −38 Original line number Diff line number Diff line Loading @@ -7879,6 +7879,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mCompatDisplayInsets; } /** * @return The {@code true} if the current instance has {@link mCompatDisplayInsets} without * considering the inheritance implemented in {@link #getCompatDisplayInsets()} */ boolean hasCompatDisplayInsetsWithoutInheritance() { return mCompatDisplayInsets != null; } /** * @return {@code true} if this activity is in size compatibility mode that uses the different * density than its parent or its bounds don't fit in parent naturally. Loading @@ -7887,7 +7895,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mInSizeCompatModeForBounds) { return true; } if (mCompatDisplayInsets == null || !shouldCreateCompatDisplayInsets() if (getCompatDisplayInsets() == null || !shouldCreateCompatDisplayInsets() // The orientation is different from parent when transforming. || isFixedRotationTransforming()) { return false; Loading Loading @@ -7958,11 +7966,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer. private void updateCompatDisplayInsets() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { mCompatDisplayInsets = mLetterboxUiController.getInheritedCompatDisplayInsets(); return; } if (mCompatDisplayInsets != null || !shouldCreateCompatDisplayInsets()) { if (getCompatDisplayInsets() != null || !shouldCreateCompatDisplayInsets()) { // The override configuration is set only once in size compatibility mode. return; } Loading Loading @@ -8025,9 +8029,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override float getCompatScale() { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { return mLetterboxUiController.getInheritedSizeCompatScale(); } return hasSizeCompatBounds() ? mSizeCompatScale : super.getCompatScale(); } Loading Loading @@ -8071,7 +8072,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveFixedOrientationConfiguration(newParentConfiguration); } if (mCompatDisplayInsets != null) { if (getCompatDisplayInsets() != null) { resolveSizeCompatModeConfiguration(newParentConfiguration); } else if (inMultiWindowMode() && !isFixedOrientationLetterboxAllowed) { // We ignore activities' requested orientation in multi-window modes. They may be Loading @@ -8089,7 +8090,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A resolveAspectRatioRestriction(newParentConfiguration); } if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null if (isFixedOrientationLetterboxAllowed || getCompatDisplayInsets() != null // In fullscreen, can be letterboxed for aspect ratio. || !inMultiWindowMode()) { updateResolvedBoundsPosition(newParentConfiguration); Loading @@ -8097,7 +8098,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A boolean isIgnoreOrientationRequest = mDisplayContent != null && mDisplayContent.getIgnoreOrientationRequest(); if (mCompatDisplayInsets == null // for size compat mode set in updateCompatDisplayInsets if (getCompatDisplayInsets() == null // for size compat mode set in updateCompatDisplayInsets // Fixed orientation letterboxing is possible on both large screen devices // with ignoreOrientationRequest enabled and on phones in split screen even with // ignoreOrientationRequest disabled. Loading Loading @@ -8143,7 +8145,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A info.neverSandboxDisplayApis(sConstrainDisplayApisConfig), info.alwaysSandboxDisplayApis(sConstrainDisplayApisConfig), !matchParentBounds(), mCompatDisplayInsets != null, getCompatDisplayInsets() != null, shouldCreateCompatDisplayInsets()); } resolvedConfig.windowConfiguration.setMaxBounds(mTmpBounds); Loading Loading @@ -8442,8 +8444,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A || orientationRespectedWithInsets)) { return; } final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets(); if (mCompatDisplayInsets != null && !mCompatDisplayInsets.mIsInFixedOrientationLetterbox) { if (compatDisplayInsets != null && !compatDisplayInsets.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 Loading Loading @@ -8498,9 +8501,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets, containingBounds, desiredAspectRatio); if (mCompatDisplayInsets != null) { mCompatDisplayInsets.getBoundsByRotation( mTmpBounds, newParentConfig.windowConfiguration.getRotation()); if (compatDisplayInsets != null) { compatDisplayInsets.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 Loading @@ -8514,7 +8517,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculate app bounds using fixed orientation bounds because they will be needed later // for comparison with size compat app bounds in {@link resolveSizeCompatModeConfiguration}. getTaskFragment().computeConfigResourceOverrides(getResolvedOverrideConfiguration(), newParentConfig, mCompatDisplayInsets); newParentConfig, compatDisplayInsets); mLetterboxBoundsForFixedOrientationAndAspectRatio = new Rect(resolvedBounds); } Loading Loading @@ -8571,13 +8574,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A ? requestedOrientation // We should use the original orientation of the activity when possible to avoid // forcing the activity in the opposite orientation. : mCompatDisplayInsets.mOriginalRequestedOrientation != ORIENTATION_UNDEFINED ? mCompatDisplayInsets.mOriginalRequestedOrientation : getCompatDisplayInsets().mOriginalRequestedOrientation != ORIENTATION_UNDEFINED ? getCompatDisplayInsets().mOriginalRequestedOrientation : newParentConfiguration.orientation; int rotation = newParentConfiguration.windowConfiguration.getRotation(); final boolean isFixedToUserRotation = mDisplayContent == null || mDisplayContent.getDisplayRotation().isFixedToUserRotation(); if (!isFixedToUserRotation && !mCompatDisplayInsets.mIsFloating) { if (!isFixedToUserRotation && !getCompatDisplayInsets().mIsFloating) { // Use parent rotation because the original display can be rotated. resolvedConfig.windowConfiguration.setRotation(rotation); } else { Loading @@ -8593,11 +8596,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // rely on them to contain the original and unchanging width and height of the app. final Rect containingAppBounds = new Rect(); final Rect containingBounds = mTmpBounds; mCompatDisplayInsets.getContainerBounds(containingAppBounds, containingBounds, rotation, getCompatDisplayInsets().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 (!mCompatDisplayInsets.mIsFloating) { if (!getCompatDisplayInsets().mIsFloating) { mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingAppBounds, containingBounds); } Loading @@ -8606,7 +8609,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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. getTaskFragment().computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, mCompatDisplayInsets); getCompatDisplayInsets()); // Use current screen layout as source because the size of app is independent to parent. resolvedConfig.screenLayout = TaskFragment.computeScreenLayoutOverride( getConfiguration().screenLayout, resolvedConfig.screenWidthDp, Loading Loading @@ -8641,14 +8644,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Calculates the scale the size compatibility bounds into the region which is available // to application. final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); final int viewportH = containerAppBounds.height(); final float lastSizeCompatScale = mSizeCompatScale; // Only allow to scale down. mSizeCompatScale = (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min((float) viewportW / contentW, (float) viewportH / contentH); updateSizeCompatScale(resolvedAppBounds, containerAppBounds); final int containerTopInset = containerAppBounds.top - containerBounds.top; final boolean topNotAligned = containerTopInset != resolvedAppBounds.top - resolvedBounds.top; Loading Loading @@ -8688,6 +8686,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A isInSizeCompatModeForBounds(resolvedAppBounds, containerAppBounds); } void updateSizeCompatScale(Rect resolvedAppBounds, Rect containerAppBounds) { // Only allow to scale down. mSizeCompatScale = mLetterboxUiController.findOpaqueNotFinishingActivityBelow() .map(activityRecord -> activityRecord.mSizeCompatScale) .orElseGet(() -> { final int contentW = resolvedAppBounds.width(); final int contentH = resolvedAppBounds.height(); final int viewportW = containerAppBounds.width(); final int viewportH = containerAppBounds.height(); return (contentW <= viewportW && contentH <= viewportH) ? 1f : Math.min( (float) viewportW / contentW, (float) viewportH / contentH); }); } private boolean isInSizeCompatModeForBounds(final Rect appBounds, final Rect containerBounds) { if (mLetterboxUiController.hasInheritedLetterboxBehavior()) { // To avoid wrong app behaviour, we decided to disable SCM when a translucent activity Loading Loading @@ -8750,10 +8762,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @Override public Rect getBounds() { // TODO(b/268458693): Refactor configuration inheritance in case of translucent activities final Rect superBounds = super.getBounds(); return mLetterboxUiController.findOpaqueNotFinishingActivityBelow() .map(ActivityRecord::getBounds) .orElseGet(() -> { if (mSizeCompatBounds != null) { return mSizeCompatBounds; } return super.getBounds(); return superBounds; }); } @Override Loading @@ -8778,7 +8796,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // Max bounds should be sandboxed when an activity should have compatDisplayInsets, and it // will keep the same bounds and screen configuration when it was first launched regardless // how its parent window changes, so that the sandbox API will provide a consistent result. if (mCompatDisplayInsets != null || shouldCreateCompatDisplayInsets()) { if (getCompatDisplayInsets() != null || shouldCreateCompatDisplayInsets()) { return true; } Loading Loading @@ -8820,7 +8838,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mTransitionController.collect(this); } } if (mCompatDisplayInsets != null) { if (getCompatDisplayInsets() != null) { Configuration overrideConfig = getRequestedOverrideConfiguration(); // Adapt to changes in orientation locking. The app is still non-resizable, but // it can change which orientation is fixed. If the fixed orientation changes, Loading Loading @@ -8896,7 +8914,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (mVisibleRequested) { // It may toggle the UI for user to restart the size compatibility mode activity. display.handleActivitySizeCompatModeIfNeeded(this); } else if (mCompatDisplayInsets != null && !visibleIgnoringKeyguard } else if (getCompatDisplayInsets() != null && !visibleIgnoringKeyguard && (app == null || !app.hasVisibleActivities())) { // visibleIgnoringKeyguard is checked to avoid clearing mCompatDisplayInsets during // displays change. Displays are turned off during the change so mVisibleRequested Loading
services/core/java/com/android/server/wm/LetterboxUiController.java +20 −20 Original line number Diff line number Diff line Loading @@ -193,12 +193,6 @@ final class LetterboxUiController { // The app compat state for the opaque activity if any private int mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; // If true it means that the opaque activity beneath a translucent one is in SizeCompatMode. private boolean mIsInheritedInSizeCompatMode; // This is the SizeCompatScale of the opaque activity beneath a translucent one private float mInheritedSizeCompatScale; // The CompatDisplayInsets of the opaque activity beneath the translucent one. private ActivityRecord.CompatDisplayInsets mInheritedCompatDisplayInsets; Loading Loading @@ -735,8 +729,21 @@ final class LetterboxUiController { : mActivityRecord.inMultiWindowMode() ? mActivityRecord.getTask().getBounds() : mActivityRecord.getRootTask().getParent().getBounds(); // In case of translucent activities an option is to use the WindowState#getFrame() of // the first opaque activity beneath. In some cases (e.g. an opaque activity is using // non MATCH_PARENT layouts or a Dialog theme) this might not provide the correct // information and in particular it might provide a value for a smaller area making // the letterbox overlap with the translucent activity's frame. // If we use WindowState#getFrame() for the translucent activity's letterbox inner // frame, the letterbox will then be overlapped with the translucent activity's frame. // Because the surface layer of letterbox is lower than an activity window, this // won't crop the content, but it may affect other features that rely on values stored // in mLetterbox, e.g. transitions, a status bar scrim and recents preview in Launcher // For this reason we use ActivityRecord#getBounds() that the translucent activity // inherits from the first opaque activity beneath and also takes care of the scaling // in case of activities in size compat mode. final Rect innerFrame = hasInheritedLetterboxBehavior() ? mActivityRecord.getWindowConfiguration().getBounds() : w.getFrame(); ? mActivityRecord.getBounds() : w.getFrame(); mLetterbox.layout(spaceToFill, innerFrame, mTmpPoint); } else if (mLetterbox != null) { mLetterbox.hide(); Loading Loading @@ -1386,10 +1393,10 @@ final class LetterboxUiController { mLetterboxConfigListener.onRemoved(); clearInheritedConfig(); } // In case mActivityRecord.getCompatDisplayInsets() is not null we don't apply the // In case mActivityRecord.hasCompatDisplayInsetsWithoutOverride() we don't apply the // opaque activity constraints because we're expecting the activity is already letterboxed. if (mActivityRecord.getTask() == null || mActivityRecord.getCompatDisplayInsets() != null || mActivityRecord.fillsParent()) { if (mActivityRecord.getTask() == null || mActivityRecord.fillsParent() || mActivityRecord.hasCompatDisplayInsetsWithoutInheritance()) { return; } final ActivityRecord firstOpaqueActivityBeneath = mActivityRecord.getTask().getActivity( Loading Loading @@ -1417,6 +1424,7 @@ final class LetterboxUiController { // We need to initialize appBounds to avoid NPE. The actual value will // be set ahead when resolving the Configuration for the activity. mutatedConfiguration.windowConfiguration.setAppBounds(new Rect()); inheritConfiguration(firstOpaqueActivityBeneath); return mutatedConfiguration; }); } Loading Loading @@ -1457,16 +1465,12 @@ final class LetterboxUiController { return mInheritedAppCompatState; } float getInheritedSizeCompatScale() { return mInheritedSizeCompatScale; } @Configuration.Orientation int getInheritedOrientation() { return mInheritedOrientation; } public ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { ActivityRecord.CompatDisplayInsets getInheritedCompatDisplayInsets() { return mInheritedCompatDisplayInsets; } Loading @@ -1486,7 +1490,7 @@ final class LetterboxUiController { * @return The first not finishing opaque activity beneath the current translucent activity * if it exists and the strategy is enabled. */ private Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { Optional<ActivityRecord> findOpaqueNotFinishingActivityBelow() { if (!hasInheritedLetterboxBehavior() || mActivityRecord.getTask() == null) { return Optional.empty(); } Loading @@ -1508,8 +1512,6 @@ final class LetterboxUiController { } mInheritedOrientation = firstOpaque.getRequestedConfigurationOrientation(); mInheritedAppCompatState = firstOpaque.getAppCompatState(); mIsInheritedInSizeCompatMode = firstOpaque.inSizeCompatMode(); mInheritedSizeCompatScale = firstOpaque.getCompatScale(); mInheritedCompatDisplayInsets = firstOpaque.getCompatDisplayInsets(); } Loading @@ -1519,8 +1521,6 @@ final class LetterboxUiController { mInheritedMaxAspectRatio = UNDEFINED_ASPECT_RATIO; mInheritedOrientation = Configuration.ORIENTATION_UNDEFINED; mInheritedAppCompatState = APP_COMPAT_STATE_CHANGED__STATE__UNKNOWN; mIsInheritedInSizeCompatMode = false; mInheritedSizeCompatScale = 1f; mInheritedCompatDisplayInsets = null; } }
services/core/java/com/android/server/wm/Task.java +5 −0 Original line number Diff line number Diff line Loading @@ -3455,6 +3455,11 @@ class Task extends TaskFragment { && top.getOrganizedTask() == this && top.isState(RESUMED); // Whether the direct top activity is in size compat mode on foreground. info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode(); if (info.topActivityInSizeCompat && mWmService.mLetterboxConfiguration.isTranslucentLetterboxingEnabled()) { // We hide the restart button in case of transparent activities. info.topActivityInSizeCompat = top.fillsParent(); } // Whether the direct top activity is eligible for letterbox education. info.topActivityEligibleForLetterboxEducation = isTopActivityResumed && top.isEligibleForLetterboxEducation(); Loading
services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +9 −3 Original line number Diff line number Diff line Loading @@ -274,7 +274,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testTranslucentActivitiesWhenUnfolding() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); setUpDisplaySizeWithApp(2800, 1400); mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mActivity.mDisplayContent.setIgnoreOrientationRequest( true /* ignoreOrientationRequest */); mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier( 1.0f /*letterboxVerticalPositionMultiplier*/); prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT); Loading @@ -285,18 +286,23 @@ public class SizeCompatTests extends WindowTestsBase { .build(); doReturn(false).when(translucentActivity).fillsParent(); mTask.addChild(translucentActivity); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN); spyOn(mActivity); // Halffold setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */); setFoldablePosture(translucentActivity, true /* isHalfFolded */, false /* isTabletop */); verify(mActivity).recomputeConfiguration(); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); clearInvocations(mActivity); // Unfold setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */); setFoldablePosture(translucentActivity, false /* isHalfFolded */, false /* isTabletop */); verify(mActivity).recomputeConfiguration(); assertEquals(translucentActivity.getBounds(), mActivity.getBounds()); } @Test Loading