diff options
| author | 2021-01-19 10:00:35 -0800 | |
|---|---|---|
| committer | 2021-01-27 20:16:53 -0800 | |
| commit | 600acce1a85f5eb407eeba558ead36220ddc1c79 (patch) | |
| tree | c7f3feaee5f30bbdb2b4773fc63c115c581bdd78 | |
| parent | d23e082711b67a6b4f9fb983cacff7563059ce86 (diff) | |
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
6 files changed, 128 insertions, 46 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 509cbdef41ff..a5cd3ffe5359 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -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 diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8841a9b87498..b41c877809e3 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -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(); } /** diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 79d112385d67..ae69ceab3d37 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -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); - } - - fitWithinBounds(outOverrideBounds, parentBounds, - (int) (density * WindowState.MINIMUM_VISIBLE_WIDTH_IN_DP), - (int) (density * WindowState.MINIMUM_VISIBLE_HEIGHT_IN_DP)); + computeFreeformBounds(outOverrideBounds, newParentConfig); + return; + } - // Prevent to overlap caption with stable insets. - final int offsetTop = parentBounds.top - outOverrideBounds.top; - if (offsetTop > 0) { - outOverrideBounds.offset(0, offsetTop); - } + 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); } } - /** - * 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 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); + } + } + + /** + * Computes bounds (letterbox or pillarbox) when the parent doesn't handle the orientation + * change and the requested orientation is different from the parent. + */ + 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 diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 3be4e78a122b..289aacde63f5 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -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(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 83282a5b8e5a..78dd4b8119e3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -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() 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 db773241f063..bc2a2fdb4eda 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -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; |