Fix letterbox in fixed rotation transform

- Use rotated bounds as outer frames of letterbox.
- Make sure app bounds related configuration is always
  computed by resolved bounds, so the app bounds won't
  keep a intermediate result while the parent hasn't
  rotated yet.
- Fix freeform size compat activity cannot move outside
  of display left side.

Fix: 151134392
Test: SizeCompatTests#testLaunchWithFixedRotationTransform
      testKeepBoundsWhenChangingFromFreeformToFullscreen

Change-Id: I0615aa4629eefcb1f666482258f6032fe3e89def
This commit is contained in:
Riddle Hsu
2020-03-10 00:26:51 +08:00
parent decfe4b331
commit 23e18a9485
3 changed files with 103 additions and 40 deletions

View File

@@ -1351,11 +1351,16 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mLetterbox.attachInput(w);
}
getPosition(mTmpPoint);
// Get the bounds of the "space-to-fill". In multi-window mode, the task-level
// represents this. In fullscreen-mode, the stack does (since the orientation letterbox
// is also applied to the task).
Rect spaceToFill = (inMultiWindowMode() || getStack() == null)
? task.getDisplayedBounds() : getStack().getDisplayedBounds();
// Get the bounds of the "space-to-fill". The transformed bounds have the highest
// priority because the activity is launched in a rotated environment. In multi-window
// mode, the task-level represents this. In fullscreen-mode, the task container does
// (since the orientation letterbox is also applied to the task).
final Rect transformedBounds = getFixedRotationTransformDisplayBounds();
final Rect spaceToFill = transformedBounds != null
? transformedBounds
: inMultiWindowMode()
? task.getDisplayedBounds()
: getRootTask().getParent().getDisplayedBounds();
mLetterbox.layout(spaceToFill, w.getFrameLw(), mTmpPoint);
} else if (mLetterbox != null) {
mLetterbox.hide();
@@ -6362,33 +6367,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mCompatDisplayInsets != null) {
resolveSizeCompatModeConfiguration(newParentConfiguration);
} else {
// We ignore activities' requested orientation in multi-window modes. Task level may
// take them into consideration when calculating bounds.
if (inMultiWindowMode()) {
// We ignore activities' requested orientation in multi-window modes. Task level may
// take them into consideration when calculating bounds.
resolvedConfig.orientation = Configuration.ORIENTATION_UNDEFINED;
}
final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
// Use tmp bounds to calculate aspect ratio so we can know whether the activity should
// use restricted size (resolvedBounds may be the requested override bounds).
mTmpBounds.setEmpty();
applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
// If the out bounds is not empty, it means the activity cannot fill parent's app
// bounds, then the relative configuration (e.g. screen size, layout) needs to be
// resolved according to the bounds.
if (!mTmpBounds.isEmpty()) {
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
resolvedBounds.set(mTmpBounds);
// Exclude the horizontal decor area because the activity will be centered
// horizontally in parent's app bounds to balance the visual appearance.
resolvedBounds.left = parentAppBounds.left;
task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
getFixedRotationTransformDisplayInfo());
final int offsetX = getHorizontalCenterOffset(
parentAppBounds.width(), resolvedBounds.width());
if (offsetX > 0) {
offsetBounds(resolvedConfig, offsetX - resolvedBounds.left, 0 /* offsetY */);
// If the activity has requested override bounds, the configuration needs to be
// computed accordingly.
if (!matchParentBounds()) {
task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration);
}
} else {
resolveFullscreenConfiguration(newParentConfiguration);
}
}
@@ -6399,6 +6388,43 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
getResolvedOverrideConfiguration().seq = mConfigurationSeq;
}
/**
* Resolves the configuration of activity in fullscreen mode. If the bounds are restricted by
* aspect ratio, the position will be centered horizontally in parent's app bounds to balance
* the visual appearance. The policy of aspect ratio has higher priority than the requested
* override bounds.
*/
private void resolveFullscreenConfiguration(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
// Use tmp bounds to calculate aspect ratio so we can know whether the activity should use
// restricted size (resolved bounds may be the requested override bounds).
mTmpBounds.setEmpty();
applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
// If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
// then there is space to be centered.
final boolean needToBeCentered = !mTmpBounds.isEmpty();
if (needToBeCentered) {
resolvedBounds.set(mTmpBounds);
// Exclude the horizontal decor area.
resolvedBounds.left = parentAppBounds.left;
}
if (!resolvedBounds.isEmpty() && !resolvedBounds.equals(parentBounds)) {
// Compute the configuration based on the resolved bounds. If aspect ratio doesn't
// restrict, the bounds should be the requested override bounds.
task.computeConfigResourceOverrides(resolvedConfig, newParentConfiguration,
getFixedRotationTransformDisplayInfo());
}
if (needToBeCentered) {
// Offset to center relative to parent's app bounds.
final int offsetX = getHorizontalCenterOffset(
parentAppBounds.width(), resolvedBounds.width());
offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
}
}
/**
* Resolves consistent screen configuration for orientation and rotation changes without
* inheriting the parent bounds.
@@ -6507,7 +6533,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
final int screenPosX = parentAppBounds.left + offsetX;
final int screenPosY = parentBounds.top;
if (screenPosX > 0 || screenPosY > 0) {
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
mSizeCompatBounds.offset(screenPosX, screenPosY);
}

View File

@@ -2127,14 +2127,26 @@ class Task extends WindowContainer<WindowContainer> {
intersectWithInsetsIfFits(outStableBounds, mTmpBounds, mTmpInsets);
}
/**
* Forces the app bounds related configuration can be computed by
* {@link #computeConfigResourceOverrides(Configuration, Configuration, DisplayInfo,
* ActivityRecord.CompatDisplayInsets)}.
*/
private static void invalidateAppBoundsConfig(@NonNull Configuration inOutConfig) {
final Rect appBounds = inOutConfig.windowConfiguration.getAppBounds();
if (appBounds != null) {
appBounds.setEmpty();
}
inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
}
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig, @Nullable DisplayInfo overrideDisplayInfo) {
if (overrideDisplayInfo != null) {
// Make sure the screen related configs can be computed by the provided display info.
inOutConfig.windowConfiguration.setAppBounds(null);
inOutConfig.screenLayout = Configuration.SCREENLAYOUT_UNDEFINED;
inOutConfig.screenWidthDp = Configuration.SCREEN_WIDTH_DP_UNDEFINED;
inOutConfig.screenHeightDp = Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, overrideDisplayInfo,
null /* compatInsets */);
@@ -2149,6 +2161,10 @@ class Task extends WindowContainer<WindowContainer> {
void computeConfigResourceOverrides(@NonNull Configuration inOutConfig,
@NonNull Configuration parentConfig,
@Nullable ActivityRecord.CompatDisplayInsets compatInsets) {
if (compatInsets != null) {
// Make sure the app bounds can be computed by the compat insets.
invalidateAppBoundsConfig(inOutConfig);
}
computeConfigResourceOverrides(inOutConfig, parentConfig, null /* overrideDisplayInfo */,
compatInsets);
}

View File

@@ -107,10 +107,16 @@ public class SizeCompatTests extends ActivityTestsBase {
setUpApp(display);
// Put app window into freeform and then make it a compat app.
mTask.setBounds(100, 100, 400, 600);
final Rect bounds = new Rect(100, 100, 400, 600);
mTask.setBounds(bounds);
prepareUnresizable(-1.f /* maxAspect */, SCREEN_ORIENTATION_PORTRAIT);
assertEquals(bounds, mActivity.getBounds());
// The activity should be able to accept negative x position [-150, 100 - 150, 600].
final int dx = bounds.left + bounds.width() / 2;
mTask.setBounds(bounds.left - dx, bounds.top, bounds.right - dx, bounds.bottom);
assertEquals(mTask.getBounds(), mActivity.getBounds());
final Rect bounds = new Rect(mActivity.getBounds());
final int density = mActivity.getConfiguration().densityDpi;
// change display configuration to fullscreen
@@ -231,11 +237,7 @@ public class SizeCompatTests extends ActivityTestsBase {
// The position should be horizontal centered.
assertEquals((displayWidth - bounds.width()) / 2, bounds.left);
final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
mService.mWindowManager, mock(Session.class), new TestIWindow(),
new WindowManager.LayoutParams(), mActivity);
mActivity.addWindow(w);
mActivity.mDisplayContent.mInputMethodTarget = w;
mActivity.mDisplayContent.mInputMethodTarget = addWindowToActivity(mActivity);
// Make sure IME cannot attach to the app, otherwise IME window will also be shifted.
assertFalse(mActivity.mDisplayContent.isImeAttachedToApp());
}
@@ -475,6 +477,25 @@ public class SizeCompatTests extends ActivityTestsBase {
assertEquals((int) (dw * maxAspect), mActivity.getBounds().width());
// The bounds should be horizontal centered: (2500-1900)/2=350.
assertEquals((dh - mActivity.getBounds().width()) / 2, mActivity.getBounds().left);
// The letterbox needs a main window to layout.
addWindowToActivity(mActivity);
// Compute the frames of the window and invoke {@link ActivityRecord#layoutLetterbox}.
mActivity.mRootWindowContainer.performSurfacePlacement(false /* recoveringMemory */);
// The letterbox insets should be [350, 0 - 350, 0].
assertEquals(new Rect(mActivity.getBounds().left, 0, dh - mActivity.getBounds().right, 0),
mActivity.getLetterboxInsets());
}
private WindowState addWindowToActivity(ActivityRecord activity) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
final WindowTestUtils.TestWindowState w = new WindowTestUtils.TestWindowState(
mService.mWindowManager, mock(Session.class), new TestIWindow(), params, mActivity);
WindowTestsBase.makeWindowVisible(w);
w.mWinAnimator.mDrawState = WindowStateAnimator.HAS_DRAWN;
mActivity.addWindow(w);
return w;
}
/**