diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index e2253e7f83ceb..6cf36d6925203 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -88,6 +88,8 @@ import static android.os.Build.VERSION_CODES.HONEYCOMB; import static android.os.Build.VERSION_CODES.O; import static android.os.Process.SYSTEM_UID; import static android.view.Display.INVALID_DISPLAY; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; import static com.android.server.am.ActivityRecordProto.CONFIGURATION_CONTAINER; import static com.android.server.am.ActivityRecordProto.FRONT_OF_TASK; @@ -195,6 +197,7 @@ import android.util.Slog; import android.util.TimeUtils; import android.util.proto.ProtoOutputStream; import android.view.AppTransitionAnimationSpec; +import android.view.DisplayCutout; import android.view.IAppTransitionAnimationSpecsFuture; import android.view.IApplicationToken; import android.view.RemoteAnimationDefinition; @@ -382,6 +385,12 @@ final class ActivityRecord extends ConfigurationContainer { private int[] mHorizontalSizeConfigurations; private int[] mSmallestSizeConfigurations; + /** + * The precomputed display insets for resolving configuration. It will be non-null if + * {@link #shouldUseSizeCompatMode} returns {@code true}. + */ + private CompatDisplayInsets mCompatDisplayInsets; + boolean pendingVoiceInteractionStart; // Waiting for activity-invoked voice session IVoiceInteractionSession voiceSession; // Voice interaction session for this activity @@ -2833,6 +2842,11 @@ final class ActivityRecord extends ConfigurationContainer { // 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 = parentConfig.smallestScreenWidthDp; + + final ActivityDisplay display = getDisplay(); + if (display != null && display.mDisplayContent != null) { + mCompatDisplayInsets = new CompatDisplayInsets(display.mDisplayContent); + } } } onRequestedOverrideConfigurationChanged(overrideConfig); @@ -2849,7 +2863,7 @@ final class ActivityRecord extends ConfigurationContainer { super.resolveOverrideConfiguration(newParentConfiguration); if (hasOverrideBounds) { task.computeConfigResourceOverrides(getResolvedOverrideConfiguration(), - newParentConfiguration, true /* insideParentBounds */); + newParentConfiguration); } } @@ -2922,9 +2936,8 @@ final class ActivityRecord extends ConfigurationContainer { resolvedBounds.right -= resolvedAppBounds.left; } - // In size compatibility mode, activity is allowed to have larger bounds than its parent. task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration, - false /* insideParentBounds */); + mCompatDisplayInsets); // 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) { @@ -3450,6 +3463,7 @@ final class ActivityRecord extends ConfigurationContainer { // configuration. getRequestedOverrideConfiguration().setToDefaults(); getResolvedOverrideConfiguration().setToDefaults(); + mCompatDisplayInsets = null; if (visible) { // Configuration will be ensured when becoming visible, so if it is already visible, // then the manual update is needed. @@ -3796,4 +3810,46 @@ final class ActivityRecord extends ConfigurationContainer { writeToProto(proto); proto.end(token); } + + /** + * 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. + */ + static class CompatDisplayInsets { + final int mDisplayWidth; + final int mDisplayHeight; + + /** The nonDecorInsets for each rotation. Includes the navigation bar and cutout insets. */ + final Rect[] mNonDecorInsets = new Rect[4]; + /** + * The stableInsets for each rotation. Includes the status bar inset and the + * nonDecorInsets. It is used to compute {@link Configuration#screenWidthDp} and + * {@link Configuration#screenHeightDp}. + */ + final Rect[] mStableInsets = new Rect[4]; + + CompatDisplayInsets(DisplayContent display) { + mDisplayWidth = display.mBaseDisplayWidth; + mDisplayHeight = display.mBaseDisplayHeight; + final DisplayPolicy policy = display.getDisplayPolicy(); + final DisplayCutout cutout = display.getDisplayInfo().displayCutout; + for (int rotation = 0; rotation < 4; rotation++) { + mNonDecorInsets[rotation] = new Rect(); + mStableInsets[rotation] = new Rect(); + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated ? mDisplayHeight : mDisplayWidth; + final int dh = rotated ? mDisplayWidth : mDisplayHeight; + policy.getNonDecorInsetsLw(rotation, dw, dh, cutout, mNonDecorInsets[rotation]); + mStableInsets[rotation].set(mNonDecorInsets[rotation]); + policy.convertNonDecorInsetsToStableInsets(mStableInsets[rotation], rotation); + } + } + + void getDisplayBounds(Rect outBounds, int rotation) { + final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270); + final int dw = rotated ? mDisplayHeight : mDisplayWidth; + final int dh = rotated ? mDisplayWidth : mDisplayHeight; + outBounds.set(0, 0, dw, dh); + } + } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 6ae772003a9aa..1934e2508c9b7 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2774,6 +2774,16 @@ public class DisplayPolicy { return mShowingDream; } + /** + * Calculates the stable insets if we already have the non-decor insets. + * + * @param inOutInsets The known non-decor insets. It will be modified to stable insets. + * @param rotation The current display rotation. + */ + void convertNonDecorInsetsToStableInsets(Rect inOutInsets, int rotation) { + inOutInsets.top = Math.max(inOutInsets.top, mStatusBarHeightForRotation[rotation]); + } + /** * Calculates the stable insets without running a layout. * @@ -2789,7 +2799,7 @@ public class DisplayPolicy { // Navigation bar and status bar. getNonDecorInsetsLw(displayRotation, displayWidth, displayHeight, displayCutout, outInsets); - outInsets.top = Math.max(outInsets.top, mStatusBarHeightForRotation[displayRotation]); + convertNonDecorInsetsToStableInsets(outInsets, displayRotation); } /** diff --git a/services/core/java/com/android/server/wm/TaskRecord.java b/services/core/java/com/android/server/wm/TaskRecord.java index 9713befc91f65..15060e1fc712e 100644 --- a/services/core/java/com/android/server/wm/TaskRecord.java +++ b/services/core/java/com/android/server/wm/TaskRecord.java @@ -22,6 +22,7 @@ import static android.app.ActivityTaskManager.RESIZE_MODE_FORCED; import static android.app.ActivityTaskManager.RESIZE_MODE_SYSTEM; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; @@ -2048,15 +2049,12 @@ class TaskRecord extends ConfigurationContainer { } mTmpBounds.set(0, 0, displayInfo.logicalWidth, displayInfo.logicalHeight); - policy.getStableInsetsLw(displayInfo.rotation, - displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout, - mTmpInsets); - intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); - - policy.getNonDecorInsetsLw(displayInfo.rotation, - displayInfo.logicalWidth, displayInfo.logicalHeight, displayInfo.displayCutout, - mTmpInsets); + policy.getNonDecorInsetsLw(displayInfo.rotation, displayInfo.logicalWidth, + displayInfo.logicalHeight, displayInfo.displayCutout, mTmpInsets); intersectWithInsetsIfFits(outNonDecorBounds, mTmpBounds, mTmpInsets); + + policy.convertNonDecorInsetsToStableInsets(mTmpInsets, displayInfo.rotation); + intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets); } /** @@ -2073,7 +2071,7 @@ class TaskRecord extends ConfigurationContainer { void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, @NonNull Configuration parentConfig) { - computeConfigResourceOverrides(inOutConfig, parentConfig, true /* insideParentBounds */); + computeConfigResourceOverrides(inOutConfig, parentConfig, null /* compatInsets */); } /** @@ -2085,7 +2083,8 @@ class TaskRecord extends ConfigurationContainer { * just be inherited from the parent configuration. **/ void computeConfigResourceOverrides(@NonNull Configuration inOutConfig, - @NonNull Configuration parentConfig, boolean insideParentBounds) { + @NonNull Configuration parentConfig, + @Nullable ActivityRecord.CompatDisplayInsets compatInsets) { int windowingMode = inOutConfig.windowConfiguration.getWindowingMode(); if (windowingMode == WINDOWING_MODE_UNDEFINED) { windowingMode = parentConfig.windowConfiguration.getWindowingMode(); @@ -2103,6 +2102,9 @@ class TaskRecord extends ConfigurationContainer { inOutConfig.windowConfiguration.setAppBounds(bounds); outAppBounds = inOutConfig.windowConfiguration.getAppBounds(); } + // Non-null compatibility insets means the activity prefers to keep its original size, so + // the out bounds doesn't need to be restricted by the parent. + final boolean insideParentBounds = compatInsets == null; if (insideParentBounds && windowingMode != WINDOWING_MODE_FREEFORM) { final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds(); if (parentAppBounds != null && !parentAppBounds.isEmpty()) { @@ -2125,6 +2127,17 @@ class TaskRecord extends ConfigurationContainer { // Set to app bounds because it excludes decor insets. mTmpNonDecorBounds.set(outAppBounds); mTmpStableBounds.set(outAppBounds); + + // Apply the given non-decor and stable insets to calculate the corresponding bounds + // for screen size of configuration. + final int rotation = parentConfig.windowConfiguration.getRotation(); + if (rotation != ROTATION_UNDEFINED && compatInsets != null) { + compatInsets.getDisplayBounds(mTmpBounds, rotation); + intersectWithInsetsIfFits(mTmpNonDecorBounds, mTmpBounds, + compatInsets.mNonDecorInsets[rotation]); + intersectWithInsetsIfFits(mTmpStableBounds, mTmpBounds, + compatInsets.mStableInsets[rotation]); + } } if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) { 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 32e96a592207d..44390b0c8d433 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -57,6 +57,7 @@ import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.util.MergedConfiguration; import android.util.MutableBoolean; +import android.view.DisplayInfo; import androidx.test.filters.MediumTest; @@ -87,6 +88,10 @@ public class ActivityRecordTests extends ActivityTestsBase { doReturn(false).when(mService).isBooting(); doReturn(true).when(mService).isBooted(); + + final DisplayContent displayContent = mStack.getDisplay().mDisplayContent; + doReturn(mock(DisplayPolicy.class)).when(displayContent).getDisplayPolicy(); + doReturn(mock(DisplayInfo.class)).when(displayContent).getDisplayInfo(); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java index dc307b547a63f..d87eed26b2116 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java @@ -25,7 +25,10 @@ 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.util.DisplayMetrics.DENSITY_DEFAULT; +import static android.view.Surface.ROTATION_0; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.server.wm.WindowContainer.POSITION_TOP; import static org.hamcrest.Matchers.not; @@ -35,6 +38,8 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import android.app.ActivityManager; @@ -357,6 +362,7 @@ public class TaskRecordTests extends ActivityTestsBase { parentConfig.densityDpi = 400; parentConfig.screenHeightDp = 200; // 200 * 400 / 160 = 500px parentConfig.screenWidthDp = 100; // 100 * 400 / 160 = 250px + parentConfig.windowConfiguration.setRotation(ROTATION_0); // Portrait bounds. inOutConfig.windowConfiguration.getBounds().set(0, 0, shortSide, longSide); @@ -370,12 +376,27 @@ public class TaskRecordTests extends ActivityTestsBase { inOutConfig.setToDefaults(); // Landscape bounds. inOutConfig.windowConfiguration.getBounds().set(0, 0, longSide, shortSide); + + // Setup the display with a top stable inset. The later assertion will ensure the inset is + // excluded from screenHeightDp. + final int statusBarHeight = 100; + final DisplayContent displayContent = mock(DisplayContent.class); + final DisplayPolicy policy = mock(DisplayPolicy.class); + doAnswer(invocationOnMock -> { + final Rect insets = invocationOnMock.getArgument(0); + insets.top = statusBarHeight; + return null; + }).when(policy).convertNonDecorInsetsToStableInsets(any(), eq(ROTATION_0)); + doReturn(policy).when(displayContent).getDisplayPolicy(); + doReturn(mock(DisplayInfo.class)).when(displayContent).getDisplayInfo(); + // Without limiting to be inside the parent bounds, the out screen size should keep relative // to the input bounds. - task.computeConfigResourceOverrides(inOutConfig, parentConfig, - false /* insideParentBounds */); + final ActivityRecord.CompatDisplayInsets compatIntsets = + new ActivityRecord.CompatDisplayInsets(displayContent); + task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets); - assertEquals(shortSide * DENSITY_DEFAULT / parentConfig.densityDpi, + assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi, inOutConfig.screenHeightDp); assertEquals(longSide * DENSITY_DEFAULT / parentConfig.densityDpi, inOutConfig.screenWidthDp);