Ensure consistency of screenHeightDp for non-resizable activity
Originally the height of status bar (or other stable insets) is not excluded from screenHeightDp for size compatibility mode activity because the previous display where the activity resides may be removed or changed then there is no available and consistent display bounds to calculate the screen configuration. Now we precompute and store the display insets in each rotation, so the activity always has the original display insets to compute consistent screen configuration. Test: atest ActivityRecordTests Test: atest TaskRecordTests#testComputeConfigResourceOverrides Bug: 112288258 Change-Id: I5c769f8080fc5ae4eba966d2fa42cb650254074d
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.<Rect>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);
|
||||
|
||||
Reference in New Issue
Block a user