diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 0b4977ae4f1ef..0b5bd7a1b5668 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -6492,8 +6492,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mLastReportedConfiguration.setConfiguration(global, override); } - boolean hasCompatDisplayInsets() { - return mCompatDisplayInsets != null; + @Nullable + CompatDisplayInsets getCompatDisplayInsets() { + return mCompatDisplayInsets; } /** @@ -7816,13 +7817,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A /** * The precomputed insets of the display in each rotation. This is used to make the size * compatibility mode activity compute the configuration without relying on its current display. - * This currently only supports fullscreen and freeform windowing mode. */ static class CompatDisplayInsets { + /** The container width on rotation 0. */ private final int mWidth; + /** The container height on rotation 0. */ private final int mHeight; + /** Whether the {@link Task} windowingMode represents a floating window*/ final boolean mIsFloating; - + /** Whether the {@link Task} is letterboxed when the unresizable activity is first shown. */ + final boolean mIsTaskLetterboxed; /** * The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. It * is used to compute the appBounds. @@ -7849,27 +7853,24 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mNonDecorInsets[rotation] = emptyRect; mStableInsets[rotation] = emptyRect; } + mIsTaskLetterboxed = false; return; } final Task task = container.getTask(); - if (task != null && task.isTaskLetterboxed()) { - // For apps in Task letterbox, it should fill the task bounds. - final Point dimensions = getRotationZeroDimensions(task); - mWidth = dimensions.x; - mHeight = dimensions.y; - } else { - // If the activity is not floating nor letterboxed, assume it fills the root. - final RootDisplayArea root = container.getRootDisplayArea(); - if (root == null || root == display) { - mWidth = display.mBaseDisplayWidth; - mHeight = display.mBaseDisplayHeight; - } else { - final Point dimensions = getRotationZeroDimensions(root); - mWidth = dimensions.x; - mHeight = dimensions.y; - } - } + mIsTaskLetterboxed = task != null && task.isTaskLetterboxed(); + + // Store the bounds of the Task for the non-resizable activity to use in size compat + // mode so that the activity will not be resized regardless the windowing mode it is + // currently in. + final WindowContainer filledContainer = task != null ? task : display; + final Point dimensions = getRotationZeroDimensions(filledContainer); + mWidth = dimensions.x; + mHeight = dimensions.y; + + // Bounds of the filled container if it doesn't fill the display. + final Rect unfilledContainerBounds = + filledContainer.getBounds().equals(display.getBounds()) ? null : new Rect(); final DisplayPolicy policy = display.getDisplayPolicy(); for (int rotation = 0; rotation < 4; rotation++) { mNonDecorInsets[rotation] = new Rect(); @@ -7882,6 +7883,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]); mStableInsets[rotation].set(mNonDecorInsets[rotation]); policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation); + + if (unfilledContainerBounds == null) { + continue; + } + // The insets is based on the display, but the container may be smaller than the + // display, so update the insets to exclude parts that are not intersected with the + // container. + unfilledContainerBounds.set(filledContainer.getBounds()); + display.rotateBounds( + filledContainer.getConfiguration().windowConfiguration.getRotation(), + rotation, + unfilledContainerBounds); + updateInsetsForBounds(unfilledContainerBounds, dw, dh, mNonDecorInsets[rotation]); + updateInsetsForBounds(unfilledContainerBounds, dw, dh, mStableInsets[rotation]); } } @@ -7899,6 +7914,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A return rotated ? new Point(height, width) : new Point(width, height); } + /** + * Updates the display insets to exclude the parts that are not intersected with the given + * bounds. + */ + private static void updateInsetsForBounds(Rect bounds, int displayWidth, int displayHeight, + Rect inset) { + inset.left = Math.max(0, inset.left - bounds.left); + inset.top = Math.max(0, inset.top - bounds.top); + inset.right = Math.max(0, bounds.right - displayWidth + inset.right); + inset.bottom = Math.max(0, bounds.bottom - displayHeight + inset.bottom); + } + void getBoundsByRotation(Rect outBounds, int rotation) { final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); final int dw = rotated ? mHeight : mWidth; diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 637240c06de2c..6d2b0bf4fb5c6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2825,9 +2825,7 @@ class Task extends WindowContainer { getResolvedOverrideConfiguration().windowConfiguration.getBounds(); if (windowingMode == WINDOWING_MODE_FULLSCREEN) { - computeFullscreenBounds(outOverrideBounds, null /* refActivity */, - newParentConfig.windowConfiguration.getBounds(), - newParentConfig.orientation); + computeFullscreenBounds(outOverrideBounds, newParentConfig); // The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if // the parent or display is smaller than the size, the content may be cropped. return; @@ -2867,19 +2865,19 @@ class Task extends WindowContainer { * {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN} when the parent doesn't handle the * orientation change and the requested orientation is different from the parent. */ - void computeFullscreenBounds(@NonNull Rect outBounds, @Nullable ActivityRecord refActivity, - @NonNull Rect parentBounds, int parentOrientation) { + void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) { // In FULLSCREEN mode, always start with empty bounds to indicate "fill parent". outBounds.setEmpty(); if (handlesOrientationChangeFromDescendant()) { + // No need to letterbox at task level. Display will handle fixed-orientation requests. return; } - if (refActivity == null) { - // Use the top activity as the reference of orientation. Don't include overlays because - // it is usually not the actual content or just temporarily shown. - // E.g. ForcedResizableInfoActivity. - refActivity = getTopNonFinishingActivity(false /* includeOverlays */); - } + + final int parentOrientation = newParentConfig.orientation; + // Use the top activity as the reference of orientation. Don't include overlays because + // it is usually not the actual content or just temporarily shown. + // E.g. ForcedResizableInfoActivity. + final ActivityRecord refActivity = getTopNonFinishingActivity(false /* includeOverlays */); // If the task or the reference activity requires a different orientation (either by // override or activityInfo), make it fit the available bounds by scaling down its bounds. @@ -2891,11 +2889,17 @@ class Task extends WindowContainer { return; } - if (refActivity != null && refActivity.hasCompatDisplayInsets()) { + final ActivityRecord.CompatDisplayInsets compatDisplayInsets = + refActivity == null ? null : refActivity.getCompatDisplayInsets(); + if (compatDisplayInsets != null && !compatDisplayInsets.mIsTaskLetterboxed) { // App prefers to keep its original size. + // If the size compat is from previous task letterboxing, we may want to have task + // letterbox again, otherwise it will show the size compat restart button even if the + // restart bounds will be the same. return; } + final Rect parentBounds = newParentConfig.windowConfiguration.getBounds(); final int parentWidth = parentBounds.width(); final int parentHeight = parentBounds.height(); float aspect = Math.max(parentWidth, parentHeight) @@ -2930,6 +2934,18 @@ class Task extends WindowContainer { final int left = parentBounds.centerX() - width / 2; outBounds.set(left, parentBounds.top, left + width, parentBounds.bottom); } + + if (compatDisplayInsets != null) { + compatDisplayInsets.getBoundsByRotation( + mTmpBounds, newParentConfig.windowConfiguration.getRotation()); + if (outBounds.width() != mTmpBounds.width() + || outBounds.height() != mTmpBounds.height()) { + // The app shouldn't be resized, we only do task letterboxing if the compat bounds + // is also from the same task letterbox. Otherwise, clear the task bounds to show + // app in size compat mode. + outBounds.setEmpty(); + } + } } Rect updateOverrideConfigurationFromLaunchBounds() { 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 e19024825b382..22430a110ca3e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -20,8 +20,10 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; +import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; +import static android.view.SurfaceProto.ROTATION_180; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; @@ -39,7 +41,6 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.clearInvocations; @@ -243,7 +244,7 @@ public class SizeCompatTests extends WindowTestsBase { // The bounds should be [800, 0 - 1800, 2500]. assertEquals(origBounds.width(), currentBounds.width()); assertEquals(origBounds.height(), currentBounds.height()); - assertEquals(Configuration.ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); + assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); assertEquals(Configuration.ORIENTATION_PORTRAIT, mActivity.getConfiguration().orientation); // The previous resize operation doesn't consider the rotation change after size changed. @@ -729,7 +730,7 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any(), any(), anyInt()); + verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); @@ -770,7 +771,7 @@ public class SizeCompatTests extends WindowTestsBase { // Update with new activity requested orientation and recompute bounds with no previous // size compat cache. verify(mTask).onDescendantOrientationChanged(same(newActivity)); - verify(mTask).computeFullscreenBounds(any(), any(), any(), anyInt()); + verify(mTask).computeFullscreenBounds(any(), any()); final Rect displayBounds = new Rect(display.getBounds()); final Rect taskBounds = new Rect(mTask.getBounds()); @@ -821,6 +822,88 @@ public class SizeCompatTests extends WindowTestsBase { assertEquals(activityBounds, mActivity.getBounds()); } + @Test + public void testDisplayIgnoreOrientationRequest_rotated180_notInSizeCompat() { + // Set up a display in landscape and ignoring orientation request. + setUpDisplaySizeWithApp(2800, 1400); + final DisplayContent display = mActivity.mDisplayContent; + display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Portrait fixed app. + prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT); + + // In Task letterbox + assertTrue(mTask.isTaskLetterboxed()); + assertFalse(mActivity.inSizeCompatMode()); + + // Rotate display to portrait. + rotateDisplay(display, ROTATION_90); + + // App should be in size compat. + assertFalse(mTask.isTaskLetterboxed()); + assertScaled(); + + // Rotate display to landscape. + rotateDisplay(display, ROTATION_180); + + // In Task letterbox + assertTrue(mTask.isTaskLetterboxed()); + assertFalse(mActivity.inSizeCompatMode()); + } + + @Test + public void testDisplayIgnoreOrientationRequestWithInsets_rotated180_notInSizeCompat() { + // Set up a display in portrait with display cutout and ignoring orientation request. + final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, 2800) + .setNotch(75) + .build(); + setUpApp(display); + display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + + // Landscape fixed app. + prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE); + + // In Task letterbox + assertTrue(mTask.isTaskLetterboxed()); + assertFalse(mActivity.inSizeCompatMode()); + + // Rotate display to portrait. + rotateDisplay(display, ROTATION_90); + + // App should be in size compat. + assertFalse(mTask.isTaskLetterboxed()); + assertScaled(); + + // Rotate display to landscape. + rotateDisplay(display, ROTATION_180); + + // In Task letterbox + assertTrue(mTask.isTaskLetterboxed()); + assertFalse(mActivity.inSizeCompatMode()); + } + + @Test + public void testTaskDisplayAreaNotFillDisplay() { + setUpDisplaySizeWithApp(1400, 2800); + final DisplayContent display = mActivity.mDisplayContent; + final TaskDisplayArea taskDisplayArea = mActivity.getDisplayArea(); + taskDisplayArea.setBounds(0, 0, 1000, 2400); + + // Portrait fixed app. + prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_LANDSCAPE); + + final Rect displayBounds = new Rect(display.getBounds()); + assertEquals(ORIENTATION_LANDSCAPE, display.getConfiguration().orientation); + assertEquals(2800, displayBounds.width()); + assertEquals(1400, displayBounds.height()); + taskDisplayArea.setBounds(0, 0, 2400, 1000); + + final Rect activityBounds = new Rect(mActivity.getBounds()); + assertFalse(mActivity.inSizeCompatMode()); + assertEquals(2400, activityBounds.width()); + assertEquals(1000, activityBounds.height()); + } + private static WindowState addWindowToActivity(ActivityRecord activity) { final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;