Letterbox: configurable rounded corners.

Also, allow a transparent appearance of status and navigation bars when rounded corners are enabled for test purposes. The final goal is to make status bar a semi-transparent black in the letterbox mode when a transparent status bar is requested even when corners aren't rounded.

To enable rounded corners run (e.g. set radius to 50):
adb shell cmd window set-letterbox-activity-corners-radius <radius>

To get corners radius run:
adb shell cmd window get-letterbox-activity-corners-radius

To reset corners radius run:
adb shell cmd window set-letterbox-activity-corners-radius reset

If radius <= 0 then rounded corners will be disabled.

Test: adb shell cmd window (set|get)-letterbox-activity-corners-radius
Bug: 170216100
Change-Id: I0cbd3d0966f80d3623451a40b7f073496d2cc516
This commit is contained in:
Mariia Sandrikova
2021-01-27 16:05:47 +00:00
parent e363da1269
commit 22045dbab7
9 changed files with 211 additions and 12 deletions

View File

@@ -4626,6 +4626,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>

View File

@@ -4131,6 +4131,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" />

View File

@@ -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();
}
/**

View File

@@ -56,6 +56,6 @@ public class BarController {
if (win == null) {
return true;
}
return win.letterboxNotIntersectsOrFullyContains(getContentFrame(win));
return win.isTransparentBarAllowed(getContentFrame(win));
}
}

View File

@@ -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. */

View File

@@ -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(

View File

@@ -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) {

View File

@@ -3807,11 +3807,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) {

View File

@@ -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);
@@ -164,6 +170,41 @@ public class LetterboxTest {
verify(mTransaction).setPosition(mSurfaces.top, -1000, -2000);
}
@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));
@@ -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();