diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 65d5c0490cfc655ec50928f650c908875ade3cce..78cb8d0f2ace2eb0ea41fd764e66a84bfe07a229 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -76,7 +76,6 @@ import static android.content.pm.ActivityInfo.FLAG_NO_HISTORY; import static android.content.pm.ActivityInfo.FLAG_SHOW_FOR_ALL_USERS; import static android.content.pm.ActivityInfo.FLAG_STATE_NOT_NEEDED; import static android.content.pm.ActivityInfo.FLAG_TURN_SCREEN_ON; -import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; import static android.content.pm.ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED; import static android.content.pm.ActivityInfo.LAUNCH_MULTIPLE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; @@ -86,6 +85,7 @@ import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_IF_ALLOWLISTED; import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER; +import static android.content.pm.ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE; import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; @@ -311,6 +311,7 @@ import android.content.pm.UserProperties; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; @@ -352,6 +353,7 @@ import android.view.RemoteAnimationTarget; import android.view.Surface.Rotation; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.view.WindowInsets; import android.view.WindowInsets.Type; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; @@ -8533,6 +8535,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (isFixedOrientationLetterboxAllowed) { resolveFixedOrientationConfiguration(newParentConfiguration); } + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration. + // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. + if (!isLetterboxedForFixedOrientationAndAspectRatio() + && !mLetterboxUiController.hasFullscreenOverride()) { + resolveAspectRatioRestriction(newParentConfiguration); + } final CompatDisplayInsets compatDisplayInsets = getCompatDisplayInsets(); if (compatDisplayInsets != null) { resolveSizeCompatModeConfiguration(newParentConfiguration, compatDisplayInsets); @@ -8545,14 +8554,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (!matchParentBounds()) { computeConfigByResolveHint(resolvedConfig, newParentConfiguration); } - // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds - // are already calculated in resolveFixedOrientationConfiguration, or if in size compat - // mode, it should already be calculated in resolveSizeCompatModeConfiguration. - // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. - } - if (!isLetterboxedForFixedOrientationAndAspectRatio() && !mInSizeCompatModeForBounds - && !mLetterboxUiController.hasFullscreenOverride()) { - resolveAspectRatioRestriction(newParentConfiguration); } if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null @@ -8764,7 +8765,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION; } // Letterbox for limited aspect ratio. - if (mIsAspectRatioApplied) { + if (isLetterboxedForAspectRatioOnly()) { return APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO; } @@ -8793,13 +8794,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds(); final float screenResolvedBoundsWidth = screenResolvedBounds.width(); final float parentAppBoundsWidth = parentAppBounds.width(); + final boolean isImmersiveMode = isImmersiveMode(parentBounds); + final Insets navBarInsets; + if (isImmersiveMode) { + navBarInsets = mDisplayContent.getInsetsStateController() + .getRawInsetsState().calculateInsets( + parentBounds, + WindowInsets.Type.navigationBars(), + true /* ignoreVisibility */); + } else { + navBarInsets = Insets.NONE; + } // Horizontal position int offsetX = 0; if (parentBounds.width() != screenResolvedBoundsWidth) { if (screenResolvedBoundsWidth <= parentAppBoundsWidth) { float positionMultiplier = mLetterboxUiController.getHorizontalPositionMultiplier( newParentConfiguration); - offsetX = Math.max(0, (int) Math.ceil((parentAppBoundsWidth + // If in immersive mode, always align to right and overlap right insets (task bar) + // as they are transient and hidden. This removes awkward right spacing. + final int appWidth = (int) (parentAppBoundsWidth + navBarInsets.right); + offsetX = Math.max(0, (int) Math.ceil((appWidth - screenResolvedBoundsWidth) * positionMultiplier) // This is added to make sure that insets added inside // CompatDisplayInsets#getContainerBounds() do not break the alignment @@ -8819,9 +8834,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A newParentConfiguration); // If in immersive mode, always align to bottom and overlap bottom insets (nav bar, // task bar) as they are transient and hidden. This removes awkward bottom spacing. - final float newHeight = mDisplayContent.getDisplayPolicy().isImmersiveMode() - ? parentBoundsHeight : parentAppBoundsHeight; - offsetY = Math.max(0, (int) Math.ceil((newHeight + final int appHeight = (int) (parentAppBoundsHeight + navBarInsets.bottom); + offsetY = Math.max(0, (int) Math.ceil((appHeight - screenResolvedBoundsHeight) * positionMultiplier) // This is added to make sure that insets added inside // CompatDisplayInsets#getContainerBounds() do not break the alignment @@ -8841,7 +8855,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If the top is aligned with parentAppBounds add the vertical insets back so that the app // content aligns with the status bar - if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top) { + if (resolvedConfig.windowConfiguration.getAppBounds().top == parentAppBounds.top + && !isImmersiveMode) { resolvedConfig.windowConfiguration.getBounds().top = parentBounds.top; if (mSizeCompatBounds != null) { mSizeCompatBounds.top = parentBounds.top; @@ -8864,6 +8879,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + boolean isImmersiveMode(@NonNull Rect parentBounds) { + if (!mResolveConfigHint.mUseOverrideInsetsForStableBounds) { + return false; + } + final Insets navBarInsets = mDisplayContent.getInsetsStateController() + .getRawInsetsState().calculateInsets( + parentBounds, + WindowInsets.Type.navigationBars(), + false /* ignoreVisibility */); + return Insets.NONE.equals(navBarInsets); + } + @NonNull Rect getScreenResolvedBounds() { final Configuration resolvedConfig = getResolvedOverrideConfiguration(); final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds(); @@ -8906,6 +8933,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return mLetterboxBoundsForFixedOrientationAndAspectRatio != null; } + boolean isLetterboxedForAspectRatioOnly() { + return mLetterboxBoundsForAspectRatio != null; + } + boolean isAspectRatioApplied() { return mIsAspectRatioApplied; } @@ -9198,11 +9229,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // orientation bounds (stored in resolved bounds) instead of parent bounds since the // activity will be displayed within them even if it is in size compat mode. They should be // saved here before resolved bounds are overridden below. - final Rect containerBounds = isLetterboxedForFixedOrientationAndAspectRatio() + final Rect containerBounds = isAspectRatioApplied() ? new Rect(resolvedBounds) : newParentConfiguration.windowConfiguration.getBounds(); - final Rect containerAppBounds = isLetterboxedForFixedOrientationAndAspectRatio() - ? new Rect(getResolvedOverrideConfiguration().windowConfiguration.getAppBounds()) + final Rect containerAppBounds = isAspectRatioApplied() + ? new Rect(resolvedConfig.windowConfiguration.getAppBounds()) : newParentConfiguration.windowConfiguration.getAppBounds(); final int requestedOrientation = getRequestedConfigurationOrientation(); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 691176a794aead539b3f8ff5a259515231549a7b..7192a20935da64db560a7435a90147daa5863ce9 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -1686,7 +1686,7 @@ final class LetterboxUiController { if (mainWin.isLetterboxedForDisplayCutout()) { return "DISPLAY_CUTOUT"; } - if (mActivityRecord.isAspectRatioApplied()) { + if (mActivityRecord.isLetterboxedForAspectRatioOnly()) { return "ASPECT_RATIO"; } return "UNKNOWN_REASON"; 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 bf15bc89be97c6ce12904a762354acce23f1964a..c6476df1e3c78861a33a10da16150ec96720b8a3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -108,6 +108,7 @@ import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsDisabled; import android.provider.DeviceConfig; import android.provider.DeviceConfig.Properties; import android.view.InsetsFrameProvider; @@ -123,6 +124,7 @@ import com.android.internal.policy.SystemBarUtils; import com.android.internal.statusbar.LetterboxDetails; import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.wm.DeviceStateController.DeviceState; +import com.android.window.flags.Flags; import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges; import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges; @@ -187,6 +189,7 @@ public class SizeCompatTests extends WindowTestsBase { private void setUpApp(DisplayContent display) { mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build(); mActivity = mTask.getTopNonFinishingActivity(); + doReturn(false).when(mActivity).isImmersiveMode(any()); } private void setUpDisplaySizeWithApp(int dw, int dh) { @@ -395,6 +398,55 @@ public class SizeCompatTests extends WindowTestsBase { verify(translucentActivity.mLetterboxUiController).updateInheritedLetterbox(); } + // TODO(b/333663877): Enable test after fix + @Test + @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION}) + public void testRepositionLandscapeImmersiveAppWithDisplayCutout() { + final int dw = 2100; + final int dh = 2000; + final int cutoutHeight = 150; + final TestDisplayContent display = new TestDisplayContent.Builder(mAtm, dw, dh) + .setCanRotate(false) + .setNotch(cutoutHeight) + .build(); + setUpApp(display); + display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); + mWm.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(true); + + doReturn(true).when(mActivity).isImmersiveMode(any()); + prepareMinAspectRatio(mActivity, OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, + SCREEN_ORIENTATION_LANDSCAPE); + addWindowToActivity(mActivity); + mActivity.mRootWindowContainer.performSurfacePlacement(); + + final Function<ActivityRecord, Rect> innerBoundsOf = + (ActivityRecord a) -> { + final Rect bounds = new Rect(); + a.mLetterboxUiController.getLetterboxInnerBounds(bounds); + return bounds; + }; + + final Consumer<Integer> doubleClick = + (Integer y) -> { + mActivity.mLetterboxUiController.handleVerticalDoubleTap(y); + mActivity.mRootWindowContainer.performSurfacePlacement(); + }; + + final Rect bounds = mActivity.getBounds(); + assertTrue(bounds.top > cutoutHeight && bounds.bottom < dh); + assertEquals(dw, bounds.width()); + + // Double click bottom. + doubleClick.accept(dh - 10); + assertEquals(dh, innerBoundsOf.apply(mActivity).bottom); + + // Double click top. + doubleClick.accept(10); + doubleClick.accept(10); + assertEquals(cutoutHeight, innerBoundsOf.apply(mActivity).top); + } + @Test public void testResetOpaqueReferenceWhenOpaqueIsDestroyed() { mWm.mLetterboxConfiguration.setTranslucentLetterboxingOverrideEnabled(true); @@ -3994,8 +4046,7 @@ public class SizeCompatTests extends WindowTestsBase { // Prepare unresizable landscape activity prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE); - final DisplayPolicy displayPolicy = mActivity.mDisplayContent.getDisplayPolicy(); - doReturn(immersive).when(displayPolicy).isImmersiveMode(); + doReturn(immersive).when(mActivity).isImmersiveMode(any()); mActivity.mRootWindowContainer.performSurfacePlacement();