diff options
9 files changed, 211 insertions, 12 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 71b59e4d643f..5e0cda69911b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4632,6 +4632,10 @@ maximum screen area that can be occupied by the app in the letterbox mode. --> <item name="config_taskLetterboxAspectRatio" format="float" type="dimen">0.0</item> + <!-- Corners radius for activity presented the letterbox mode. Values < 0 will be ignored and + corners of the activity won't be rounded. --> + <integer name="config_letterboxActivityCornersRadius">0</integer> + <!-- If true, hide the display cutout with display area --> <bool name="config_hideDisplayCutoutWithDisplayArea">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 78a6e6232074..dfccdf4bd9a5 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4132,6 +4132,7 @@ <java-symbol type="dimen" name="controls_thumbnail_image_max_width" /> <java-symbol type="dimen" name="config_taskLetterboxAspectRatio" /> + <java-symbol type="integer" name="config_letterboxActivityCornersRadius" /> <java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" /> diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a5cd3ffe5359..3bb4c74b7dc0 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -1355,11 +1355,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final boolean surfaceReady = w.isDrawn() // Regular case || w.mWinAnimator.mSurfaceDestroyDeferred // The preserved surface is still ready. || w.isDragResizeChanged(); // Waiting for relayoutWindow to call preserveSurface. - final boolean needsLetterbox = surfaceReady && w.isLetterboxedAppWindow() && fillsParent(); + final boolean needsLetterbox = surfaceReady && isLetterboxed(w); + updateRoundedCorners(w); if (needsLetterbox) { if (mLetterbox == null) { mLetterbox = new Letterbox(() -> makeChildSurface(null), - mWmService.mTransactionFactory); + mWmService.mTransactionFactory, + mWmService::isLetterboxActivityCornersRounded); mLetterbox.attachInput(w); } getPosition(mTmpPoint); @@ -1379,6 +1381,27 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } } + /** @return {@code true} when main window is letterboxed and activity isn't transparent. */ + private boolean isLetterboxed(WindowState mainWindow) { + return mainWindow.isLetterboxedAppWindow() && fillsParent(); + } + + private void updateRoundedCorners(WindowState mainWindow) { + int cornersRadius = + // Don't round corners if letterboxed only for display cutout. + isLetterboxed(mainWindow) && !mainWindow.isLetterboxedForDisplayCutout() + ? Math.max(0, mWmService.getLetterboxActivityCornersRadius()) : 0; + setCornersRadius(mainWindow, cornersRadius); + } + + private void setCornersRadius(WindowState mainWindow, int cornersRadius) { + final SurfaceControl windowSurface = mainWindow.getClientViewRootSurface(); + if (windowSurface != null && windowSurface.isValid()) { + Transaction transaction = getPendingTransaction(); + transaction.setCornerRadius(windowSurface, cornersRadius); + } + } + void updateLetterboxSurface(WindowState winHint) { final WindowState w = findMainWindow(); if (w != winHint && winHint != null && w != null) { @@ -1408,10 +1431,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } /** - * @see Letterbox#notIntersectsOrFullyContains(Rect) + * @return {@code true} if bar shown within a given rectangle is allowed to be transparent + * when the current activity is displayed. */ - boolean letterboxNotIntersectsOrFullyContains(Rect rect) { - return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect); + boolean isTransparentBarAllowed(Rect rect) { + // TODO(b/175482966): Allow status and navigation bars to be semi-transparent black + // in letterbox mode. + return mLetterbox == null || mLetterbox.notIntersectsOrFullyContains(rect) + || mWmService.isLetterboxActivityCornersRounded(); } /** diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java index 4a90bbcc6623..eee27c72e583 100644 --- a/services/core/java/com/android/server/wm/BarController.java +++ b/services/core/java/com/android/server/wm/BarController.java @@ -56,6 +56,6 @@ public class BarController { if (win == null) { return true; } - return win.letterboxNotIntersectsOrFullyContains(getContentFrame(win)); + return win.isTransparentBarAllowed(getContentFrame(win)); } } diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java index 44ce4de529b0..02a43b74aa33 100644 --- a/services/core/java/com/android/server/wm/Letterbox.java +++ b/services/core/java/com/android/server/wm/Letterbox.java @@ -44,12 +44,17 @@ public class Letterbox { private final Supplier<SurfaceControl.Builder> mSurfaceControlFactory; private final Supplier<SurfaceControl.Transaction> mTransactionFactory; + private final Supplier<Boolean> mAreCornersRounded; private final Rect mOuter = new Rect(); private final Rect mInner = new Rect(); private final LetterboxSurface mTop = new LetterboxSurface("top"); private final LetterboxSurface mLeft = new LetterboxSurface("left"); private final LetterboxSurface mBottom = new LetterboxSurface("bottom"); private final LetterboxSurface mRight = new LetterboxSurface("right"); + // Prevents wallpaper from peeking through near rounded corners. It's not included in + // mSurfaces array since it isn't needed in methods like notIntersectsOrFullyContains + // or attachInput. + private final LetterboxSurface mBehind = new LetterboxSurface("behind"); private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom }; /** @@ -58,9 +63,11 @@ public class Letterbox { * @param surfaceControlFactory a factory for creating the managed {@link SurfaceControl}s */ public Letterbox(Supplier<SurfaceControl.Builder> surfaceControlFactory, - Supplier<SurfaceControl.Transaction> transactionFactory) { + Supplier<SurfaceControl.Transaction> transactionFactory, + Supplier<Boolean> areCornersRounded) { mSurfaceControlFactory = surfaceControlFactory; mTransactionFactory = transactionFactory; + mAreCornersRounded = areCornersRounded; } /** @@ -82,6 +89,7 @@ public class Letterbox { mLeft.layout(outer.left, outer.top, inner.left, outer.bottom, surfaceOrigin); mBottom.layout(outer.left, inner.bottom, outer.right, outer.bottom, surfaceOrigin); mRight.layout(inner.right, outer.top, outer.right, outer.bottom, surfaceOrigin); + mBehind.layout(inner.left, inner.top, inner.right, inner.bottom, surfaceOrigin); } @@ -157,6 +165,7 @@ public class Letterbox { for (LetterboxSurface surface : mSurfaces) { surface.remove(); } + mBehind.remove(); } /** Returns whether a call to {@link #applySurfaceChanges} would change the surface. */ @@ -166,6 +175,9 @@ public class Letterbox { return true; } } + if (mBehind.needsApplySurfaceChanges()) { + return true; + } return false; } @@ -173,6 +185,11 @@ public class Letterbox { for (LetterboxSurface surface : mSurfaces) { surface.applySurfaceChanges(t); } + if (mAreCornersRounded.get()) { + mBehind.applySurfaceChanges(t); + } else { + mBehind.remove(); + } } /** Enables touches to slide into other neighboring surfaces. */ diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index b6fabee33d60..8e6a778c351d 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -1012,6 +1012,9 @@ public class WindowManagerService extends IWindowManager.Stub // ignored. private float mTaskLetterboxAspectRatio; + // Corners radius for activities presented in the letterbox mode, values < 0 will be ignored. + private int mLetterboxActivityCornersRadius; + final InputManagerService mInputManager; final DisplayManagerInternal mDisplayManagerInternal; final DisplayManager mDisplayManager; @@ -1239,6 +1242,8 @@ public class WindowManagerService extends IWindowManager.Stub com.android.internal.R.bool.config_assistantOnTopOfDream); mTaskLetterboxAspectRatio = context.getResources().getFloat( com.android.internal.R.dimen.config_taskLetterboxAspectRatio); + mLetterboxActivityCornersRadius = context.getResources().getInteger( + com.android.internal.R.integer.config_letterboxActivityCornersRadius); mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); @@ -3936,6 +3941,60 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** + * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0, + * both it and a value of {@link + * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and + * and corners of the activity won't be rounded. + */ + void setLetterboxActivityCornersRadius(int cornersRadius) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + mLetterboxActivityCornersRadius = cornersRadius; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + /** + * Resets corners raidus for activities presented in the letterbox mode to {@link + * com.android.internal.R.integer.config_letterboxActivityCornersRadius}. + */ + void resetLetterboxActivityCornersRadius() { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + mLetterboxActivityCornersRadius = mContext.getResources().getInteger( + com.android.internal.R.integer.config_letterboxActivityCornersRadius); + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + /** + * Whether corners of letterboxed activities are rounded. + */ + boolean isLetterboxActivityCornersRounded() { + return getLetterboxActivityCornersRadius() > 0; + } + + /** + * Gets corners raidus for activities presented in the letterbox mode. + */ + int getLetterboxActivityCornersRadius() { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + return mLetterboxActivityCornersRadius; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + @Override public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) { mAtmInternal.enforceCallerIsRecentsOrHasPermission( diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java index a3a9c1ce9219..badd29aba968 100644 --- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java +++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java @@ -115,6 +115,10 @@ public class WindowManagerShellCommand extends ShellCommand { return runSetTaskLetterboxAspectRatio(pw); case "get-task-letterbox-aspect-ratio": return runGetTaskLetterboxAspectRatio(pw); + case "set-letterbox-activity-corners-radius": + return runSetLetterboxActivityCornersRadius(pw); + case "get-letterbox-activity-corners-radius": + return runGetLetterboxActivityCornersRadius(pw); case "reset": return runReset(pw); default: @@ -545,6 +549,38 @@ public class WindowManagerShellCommand extends ShellCommand { return 0; } + private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException { + final int cornersRadius; + try { + String arg = getNextArgRequired(); + if ("reset".equals(arg)) { + mInternal.resetLetterboxActivityCornersRadius(); + return 0; + } + cornersRadius = Integer.parseInt(arg); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: bad corners radius format " + e); + return -1; + } catch (IllegalArgumentException e) { + getErrPrintWriter().println( + "Error: 'reset' or corners radius should be provided as an argument " + e); + return -1; + } + + mInternal.setLetterboxActivityCornersRadius(cornersRadius); + return 0; + } + + private int runGetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException { + final int cornersRadius = mInternal.getLetterboxActivityCornersRadius(); + if (cornersRadius < 0) { + pw.println("Letterbox corners radius is not set"); + } else { + pw.println("Letterbox corners radius is " + cornersRadius); + } + return 0; + } + private int runReset(PrintWriter pw) throws RemoteException { int displayId = getDisplayId(getNextArg()); @@ -572,6 +608,9 @@ public class WindowManagerShellCommand extends ShellCommand { // set-task-letterbox-aspect-ratio mInternal.resetTaskLetterboxAspectRatio(); + // set-letterbox-activity-corners-radius + mInternal.resetLetterboxActivityCornersRadius(); + pw.println("Reset all settings for displayId=" + displayId); return 0; } @@ -608,6 +647,11 @@ public class WindowManagerShellCommand extends ShellCommand { + WindowManagerService.MIN_TASK_LETTERBOX_ASPECT_RATIO); pw.println(" both it and R.dimen.config_taskLetterboxAspectRatio will be ignored"); pw.println(" and framework implementation will be used to determine aspect ratio."); + pw.println(" set-letterbox-activity-corners-radius [reset|cornersRadius]"); + pw.println(" get-letterbox-activity-corners-radius"); + pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,"); + pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be"); + pw.println(" ignored and corners of the activity won't be rounded."); pw.println(" reset [-d DISPLAY_ID]"); pw.println(" Reset all override settings."); if (!IS_USER) { diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index f983f27af465..9f3188be7623 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3813,11 +3813,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } /** - * @see Letterbox#notIntersectsOrFullyContains(Rect) + * @return {@code true} if bar shown within a given frame is allowed to be transparent + * when the current window is displayed. */ - boolean letterboxNotIntersectsOrFullyContains(Rect rect) { - return mActivityRecord == null - || mActivityRecord.letterboxNotIntersectsOrFullyContains(rect); + boolean isTransparentBarAllowed(Rect frame) { + return mActivityRecord == null || mActivityRecord.isTransparentBarAllowed(frame); } public boolean isLetterboxedOverlappingWith(Rect rect) { diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java index 2f3004bf6832..a045100c4cd8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java @@ -17,6 +17,9 @@ package com.android.server.wm; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.clearInvocations; @@ -47,10 +50,12 @@ public class LetterboxTest { SurfaceControlMocker mSurfaces; SurfaceControl.Transaction mTransaction; + private boolean mAreCornersRounded = false; + @Before public void setUp() throws Exception { mSurfaces = new SurfaceControlMocker(); - mLetterbox = new Letterbox(mSurfaces, StubTransaction::new); + mLetterbox = new Letterbox(mSurfaces, StubTransaction::new, () -> mAreCornersRounded); mTransaction = spy(StubTransaction.class); } @@ -64,6 +69,7 @@ public class LetterboxTest { private static final int BOTTOM_BAR = 0x2; private static final int LEFT_BAR = 0x4; private static final int RIGHT_BAR = 0x8; + @Test public void testNotIntersectsOrFullyContains_usesGlobalCoordinates() { final Rect outer = new Rect(0, 0, 10, 50); @@ -165,6 +171,41 @@ public class LetterboxTest { } @Test + public void testApplySurfaceChanges_cornersNotRounded_surfaceBehindNotCreated() { + mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); + mLetterbox.applySurfaceChanges(mTransaction); + + assertNull(mSurfaces.behind); + } + + @Test + public void testApplySurfaceChanges_cornersRounded_surfaceBehindCreated() { + mAreCornersRounded = true; + mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); + mLetterbox.applySurfaceChanges(mTransaction); + + assertNotNull(mSurfaces.behind); + } + + @Test + public void testIsOverlappingWith_cornersRounded_doesNotCheckSurfaceBehind() { + mAreCornersRounded = true; + mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); + mLetterbox.applySurfaceChanges(mTransaction); + + assertFalse(mLetterbox.isOverlappingWith(new Rect(1, 2, 9, 9))); + } + + @Test + public void testNotIntersectsOrFullyContains_cornersRounded_doesNotCheckSurfaceBehind() { + mAreCornersRounded = true; + mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(0, 0)); + mLetterbox.applySurfaceChanges(mTransaction); + + assertTrue(mLetterbox.notIntersectsOrFullyContains(new Rect(1, 2, 9, 9))); + } + + @Test public void testSurfaceOrigin_changeCausesReapply() { mLetterbox.layout(new Rect(0, 0, 10, 10), new Rect(0, 1, 10, 10), new Point(1000, 2000)); mLetterbox.applySurfaceChanges(mTransaction); @@ -184,6 +225,8 @@ public class LetterboxTest { public SurfaceControl right; private SurfaceControl.Builder mBottomBuilder; public SurfaceControl bottom; + private SurfaceControl.Builder mBehindBuilder; + public SurfaceControl behind; @Override public SurfaceControl.Builder get() { @@ -198,6 +241,8 @@ public class LetterboxTest { mRightBuilder = (SurfaceControl.Builder) i.getMock(); } else if (((String) i.getArgument(0)).contains("bottom")) { mBottomBuilder = (SurfaceControl.Builder) i.getMock(); + } else if (((String) i.getArgument(0)).contains("behind")) { + mBehindBuilder = (SurfaceControl.Builder) i.getMock(); } return i.getMock(); }); @@ -212,6 +257,8 @@ public class LetterboxTest { right = control; } else if (i.getMock() == mBottomBuilder) { bottom = control; + } else if (i.getMock() == mBehindBuilder) { + behind = control; } return control; }).when(builder).build(); |