Allow non-resizable apps in split-screen (4/n)

1. Apply Task level letterboxing for multi window
2. Draw letterboxes if needed in multi window

Bug: 176061101
Test: atest WmTests:SizeCompatTests
Change-Id: I701cd027ad15d20e4c5ab17ef2dab892cf8bb606
This commit is contained in:
Chris Li
2021-01-19 10:00:35 -08:00
parent d23e082711
commit 600acce1a8
6 changed files with 128 additions and 46 deletions

View File

@@ -1371,7 +1371,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final Rect spaceToFill = transformedBounds != null
? transformedBounds
: inMultiWindowMode()
? task.getBounds()
? getRootTask().getBounds()
: getRootTask().getParent().getBounds();
mLetterbox.layout(spaceToFill, w.getFrame(), mTmpPoint);
} else if (mLetterbox != null) {
@@ -6589,8 +6589,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// which point, the activity type is still undefined if it will be standard.
// For other non-standard types, the type is set in the constructor, so this should
// not be a problem.
&& isActivityTypeStandardOrUndefined()
&& !mAtmService.mForceResizableActivities;
&& isActivityTypeStandardOrUndefined();
}
@Override

View File

@@ -3607,7 +3607,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
&& mImeLayeringTarget.mActivityRecord.matchParentBounds()
// IME is attached to non-Letterboxed app windows, other than windows with
// LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER flag. (Refer to WS.isLetterboxedAppWindow())
&& mImeLayeringTarget.matchesRootDisplayAreaBounds();
&& mImeLayeringTarget.matchesDisplayAreaBounds();
}
/**

View File

@@ -35,6 +35,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMAR
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.activityTypeToString;
import static android.app.WindowConfiguration.isSplitScreenWindowingMode;
import static android.app.WindowConfiguration.windowingModeToString;
import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
@@ -2865,41 +2866,61 @@ class Task extends WindowContainer<WindowContainer> {
adjustForMinimalTaskDimensions(outOverrideBounds, previousBounds, newParentConfig);
if (windowingMode == WINDOWING_MODE_FREEFORM) {
// by policy, make sure the window remains within parent somewhere
final float density =
((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
final Rect parentBounds =
new Rect(newParentConfig.windowConfiguration.getBounds());
final DisplayContent display = getDisplayContent();
if (display != null) {
// If a freeform window moves below system bar, there is no way to move it again
// by touch. Because its caption is covered by system bar. So we exclude them
// from root task bounds. and then caption will be shown inside stable area.
final Rect stableBounds = new Rect();
display.getStableRect(stableBounds);
parentBounds.intersect(stableBounds);
}
computeFreeformBounds(outOverrideBounds, newParentConfig);
return;
}
fitWithinBounds(outOverrideBounds, parentBounds,
(int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
(int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
if (isSplitScreenWindowingMode(windowingMode)
|| windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
// This is to compute whether the task should be letterboxed to handle non-resizable app
// in multi window. There is no split screen only logic.
computeLetterboxBounds(outOverrideBounds, newParentConfig);
}
}
// Prevent to overlap caption with stable insets.
final int offsetTop = parentBounds.top - outOverrideBounds.top;
if (offsetTop > 0) {
outOverrideBounds.offset(0, offsetTop);
}
/** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}. */
@VisibleForTesting
void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) {
// In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
outBounds.setEmpty();
computeLetterboxBounds(outBounds, newParentConfig);
}
/** Computes bounds for {@link WindowConfiguration#WINDOWING_MODE_FREEFORM}. */
private void computeFreeformBounds(@NonNull Rect outBounds,
@NonNull Configuration newParentConfig) {
// by policy, make sure the window remains within parent somewhere
final float density =
((float) newParentConfig.densityDpi) / DisplayMetrics.DENSITY_DEFAULT;
final Rect parentBounds =
new Rect(newParentConfig.windowConfiguration.getBounds());
final DisplayContent display = getDisplayContent();
if (display != null) {
// If a freeform window moves below system bar, there is no way to move it again
// by touch. Because its caption is covered by system bar. So we exclude them
// from root task bounds. and then caption will be shown inside stable area.
final Rect stableBounds = new Rect();
display.getStableRect(stableBounds);
parentBounds.intersect(stableBounds);
}
fitWithinBounds(outBounds, parentBounds,
(int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP),
(int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP));
// Prevent to overlap caption with stable insets.
final int offsetTop = parentBounds.top - outBounds.top;
if (offsetTop > 0) {
outBounds.offset(0, offsetTop);
}
}
/**
* Compute bounds (letterbox or pillarbox) for
* {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN} when the parent doesn't handle the
* orientation change and the requested orientation is different from the parent.
* Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation
* change and the requested orientation is different from the parent.
*/
void computeFullscreenBounds(@NonNull Rect outBounds, @NonNull Configuration newParentConfig) {
// In FULLSCREEN mode, always start with empty bounds to indicate "fill parent".
outBounds.setEmpty();
private void computeLetterboxBounds(@NonNull Rect outBounds,
@NonNull Configuration newParentConfig) {
if (handlesOrientationChangeFromDescendant()) {
// No need to letterbox at task level. Display will handle fixed-orientation requests.
return;
@@ -2957,6 +2978,8 @@ class Task extends WindowContainer<WindowContainer> {
aspect = letterboxAspectRatioOverride > MIN_TASK_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : aspect;
// Store the current bounds to be able to revert to size compat mode values below if needed.
mTmpFullBounds.set(outBounds);
if (forcedOrientation == ORIENTATION_LANDSCAPE) {
final int height = (int) Math.rint(parentWidth / aspect);
final int top = parentBounds.centerY() - height / 2;
@@ -2975,7 +2998,7 @@ class Task extends WindowContainer<WindowContainer> {
// 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();
outBounds.set(mTmpFullBounds);
}
}
}
@@ -3361,8 +3384,9 @@ class Task extends WindowContainer<WindowContainer> {
@Override
boolean handlesOrientationChangeFromDescendant() {
return super.handlesOrientationChangeFromDescendant()
// Display won't rotate for the orientation request if the TaskDisplayArea can't
// specify orientation.
// Display won't rotate for the orientation request if the Task/TaskDisplayArea
// can't specify orientation.
&& canSpecifyOrientation()
&& getDisplayArea().canSpecifyOrientation();
}
@@ -3875,7 +3899,9 @@ class Task extends WindowContainer<WindowContainer> {
}
boolean isTaskLetterboxed() {
return getWindowingMode() == WINDOWING_MODE_FULLSCREEN && !matchParentBounds();
// No letterbox for multi window root task
return !matchParentBounds()
&& (getWindowingMode() == WINDOWING_MODE_FULLSCREEN || !isRootTask());
}
@Override

View File

@@ -2108,12 +2108,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return getDisplayContent().getBounds().equals(getBounds());
}
boolean matchesRootDisplayAreaBounds() {
RootDisplayArea root = getRootDisplayArea();
if (root == null || root == getDisplayContent()) {
boolean matchesDisplayAreaBounds() {
final DisplayArea displayArea = getDisplayArea();
if (displayArea == null) {
return matchesDisplayBounds();
}
return root.getBounds().equals(getBounds());
return displayArea.getBounds().equals(getBounds());
}
/**
@@ -3760,16 +3760,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return getDisplayContent().mCurrentFocus == this;
}
/** Is this window in a container that takes up the entire screen space? */
private boolean inAppWindowThatMatchesParentBounds() {
return mActivityRecord == null || (mActivityRecord.matchParentBounds() && !inMultiWindowMode());
}
/** @return true when the window is in fullscreen mode, but has non-fullscreen bounds set, or
* is transitioning into/out-of fullscreen. */
/** @return true when the window should be letterboxed. */
boolean isLetterboxedAppWindow() {
return !inMultiWindowMode() && !matchesRootDisplayAreaBounds()
// Fullscreen mode but doesn't fill display area.
return (!inMultiWindowMode() && !matchesDisplayAreaBounds())
// Activity in size compat.
|| (mActivityRecord != null && mActivityRecord.inSizeCompatMode())
// Task letterboxed.
|| (getTask() != null && getTask().isTaskLetterboxed())
// Letterboxed for display cutout.
|| isLetterboxedForDisplayCutout();
}

View File

@@ -558,7 +558,7 @@ public class DisplayContentTests extends WindowTestsBase {
// hence isLetterboxedAppWindow() returns true.
ws.mActivityRecord.getConfiguration().windowConfiguration.setBounds(new Rect(1, 1, 1, 1));
assertFalse("matchesRootDisplayAreaBounds() should return false",
ws.matchesRootDisplayAreaBounds());
ws.matchesDisplayAreaBounds());
assertTrue("isLetterboxedAppWindow() should return true", ws.isLetterboxedAppWindow());
assertTrue("IME shouldn't be attached to app",
dc.computeImeParent() != dc.getImeTarget(IME_TARGET_LAYERING).getWindow()

View File

@@ -16,15 +16,16 @@
package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
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_180;
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;
@@ -35,6 +36,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
import static com.android.server.wm.Task.ActivityState.STOPPED;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -905,6 +907,57 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(1000, activityBounds.height());
}
@Test
public void testSupportsNonResizableInSplitScreen() {
// Support non resizable in multi window
mAtm.mSupportsNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(1000, 2800);
final TestSplitOrganizer organizer =
new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
// Non-resizable landscape activity
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
final Rect originalBounds = new Rect(mActivity.getBounds());
// Move activity to split screen
mTask.reparent(organizer.mPrimary, POSITION_TOP,
false /*moveParents*/, "test");
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mTask.getWindowingMode());
assertEquals(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, mActivity.getWindowingMode());
// Non-resizable activity in size compat mode
assertScaled();
assertEquals(originalBounds,
mActivity.getConfiguration().windowConfiguration.getBounds());
// Recompute the natural configuration of the non-resizable activity and the split screen.
mActivity.clearSizeCompatMode();
// Draw letterbox.
mActivity.setVisible(false);
mActivity.mDisplayContent.prepareAppTransition(WindowManager.TRANSIT_OPEN);
mActivity.mDisplayContent.mOpeningApps.add(mActivity);
addWindowToActivity(mActivity);
mActivity.mRootWindowContainer.performSurfacePlacement();
// Split screen is also in portrait [1000,1400], so Task should be in letterbox, and
// activity fills task.
assertEquals(ORIENTATION_LANDSCAPE, mTask.getConfiguration().orientation);
assertEquals(ORIENTATION_LANDSCAPE, mActivity.getConfiguration().orientation);
assertFitted();
assertTrue(mTask.isTaskLetterboxed());
// Letterbox should fill the gap between the split screen and the letterboxed task.
final Rect primarySplitBounds = new Rect(organizer.mPrimary.getBounds());
final Rect letterboxedTaskBounds = new Rect(mTask.getBounds());
assertTrue(primarySplitBounds.contains(letterboxedTaskBounds));
assertEquals(new Rect(letterboxedTaskBounds.left - primarySplitBounds.left,
letterboxedTaskBounds.top - primarySplitBounds.top,
primarySplitBounds.right - letterboxedTaskBounds.right,
primarySplitBounds.bottom - letterboxedTaskBounds.bottom),
mActivity.getLetterboxInsets());
}
private static WindowState addWindowToActivity(ActivityRecord activity) {
final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;