Merge "Show letterbox rounded corners behind navbar" into tm-qpr-dev
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index d2f62da..73d1ff9d 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -105,6 +105,12 @@
private final ActivityRecord mActivityRecord;
+ /**
+ * Taskbar expanded height. Used to determine when to crop an app window to display the
+ * rounded corners above the expanded taskbar.
+ */
+ private final float mExpandedTaskBarHeight;
+
/*
* WindowContainerListener responsible to make translucent activities inherit
* constraints from the first opaque activity beneath them. It's null for not
@@ -184,6 +190,9 @@
() -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled(
/* checkDeviceConfig */ true),
PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE);
+
+ mExpandedTaskBarHeight =
+ getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height);
}
/**
@@ -422,7 +431,7 @@
if (w == null) {
return;
}
- adjustBoundsForTaskbar(w, outBounds);
+ adjustBoundsIfNeeded(w, outBounds);
} else {
outBounds.setEmpty();
}
@@ -465,13 +474,13 @@
if (w == null || winHint != null && w != winHint) {
return;
}
- updateRoundedCorners(w);
+ updateRoundedCornersIfNeeded(w);
// If there is another main window that is not an application-starting window, we should
// update rounded corners for it as well, to avoid flickering rounded corners.
final WindowState nonStartingAppW = mActivityRecord.findMainWindow(
/* includeStartingApp= */ false);
if (nonStartingAppW != null && nonStartingAppW != w) {
- updateRoundedCorners(nonStartingAppW);
+ updateRoundedCornersIfNeeded(nonStartingAppW);
}
updateWallpaperForLetterbox(w);
@@ -775,8 +784,8 @@
return isSurfaceReadyAndVisible(mainWindow) && mainWindow.areAppWindowBoundsLetterboxed()
// Check for FLAG_SHOW_WALLPAPER explicitly instead of using
// WindowContainer#showWallpaper because the later will return true when this
- // activity is using blurred wallpaper for letterbox backgroud.
- && (mainWindow.mAttrs.flags & FLAG_SHOW_WALLPAPER) == 0;
+ // activity is using blurred wallpaper for letterbox background.
+ && (mainWindow.getAttrs().flags & FLAG_SHOW_WALLPAPER) == 0;
}
@VisibleForTesting
@@ -828,106 +837,107 @@
return mLetterboxConfiguration.getLetterboxBackgroundColor();
}
- private void updateRoundedCorners(WindowState mainWindow) {
+ private void updateRoundedCornersIfNeeded(final WindowState mainWindow) {
final SurfaceControl windowSurface = mainWindow.getSurfaceControl();
- if (windowSurface != null && windowSurface.isValid()) {
- final Transaction transaction = mActivityRecord.getSyncTransaction();
-
- if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
- // We don't want corner radius on the window.
- // In the case the ActivityRecord requires a letterboxed animation we never want
- // rounded corners on the window because rounded corners are applied at the
- // animation-bounds surface level and rounded corners on the window would interfere
- // with that leading to unexpected rounded corner positioning during the animation.
- transaction
- .setWindowCrop(windowSurface, null)
- .setCornerRadius(windowSurface, 0);
- return;
- }
-
- Rect cropBounds = null;
-
- if (hasVisibleTaskbar(mainWindow)) {
- cropBounds = new Rect(mActivityRecord.getBounds());
-
- // Rounded corners should be displayed above the taskbar.
- // It is important to call adjustBoundsForTaskbarUnchecked before offsetTo
- // because taskbar bounds are in screen coordinates
- adjustBoundsForTaskbarUnchecked(mainWindow, cropBounds);
-
- // Activity bounds are in screen coordinates while (0,0) for activity's surface
- // control is at the top left corner of an app window so offsetting bounds
- // accordingly.
- cropBounds.offsetTo(0, 0);
- }
-
- transaction
- .setWindowCrop(windowSurface, cropBounds)
- .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
+ if (windowSurface == null || !windowSurface.isValid()) {
+ return;
}
+
+ // cropBounds must be non-null for the cornerRadius to be ever applied.
+ mActivityRecord.getSyncTransaction()
+ .setCrop(windowSurface, getCropBoundsIfNeeded(mainWindow))
+ .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow));
}
- private boolean requiresRoundedCorners(WindowState mainWindow) {
- final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
+ @VisibleForTesting
+ @Nullable
+ Rect getCropBoundsIfNeeded(final WindowState mainWindow) {
+ if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
+ // We don't want corner radius on the window.
+ // In the case the ActivityRecord requires a letterboxed animation we never want
+ // rounded corners on the window because rounded corners are applied at the
+ // animation-bounds surface level and rounded corners on the window would interfere
+ // with that leading to unexpected rounded corner positioning during the animation.
+ return null;
+ }
+ final Rect cropBounds = new Rect(mActivityRecord.getBounds());
+
+ // It is important to call {@link #adjustBoundsIfNeeded} before {@link cropBounds.offsetTo}
+ // because taskbar bounds used in {@link #adjustBoundsIfNeeded}
+ // are in screen coordinates
+ adjustBoundsIfNeeded(mainWindow, cropBounds);
+
+ // ActivityRecord bounds are in screen coordinates while (0,0) for activity's surface
+ // control is in the top left corner of an app window so offsetting bounds
+ // accordingly.
+ cropBounds.offsetTo(0, 0);
+ return cropBounds;
+ }
+
+ private boolean requiresRoundedCorners(final WindowState mainWindow) {
return isLetterboxedNotForDisplayCutout(mainWindow)
- && mLetterboxConfiguration.isLetterboxActivityCornersRounded()
- && taskbarInsetsSource != null;
+ && mLetterboxConfiguration.isLetterboxActivityCornersRounded();
}
// Returns rounded corners radius the letterboxed activity should have based on override in
// R.integer.config_letterboxActivityCornersRadius or min device bottom corner radii.
- // Device corners can be different on the right and left sides but we use the same radius
+ // Device corners can be different on the right and left sides, but we use the same radius
// for all corners for consistency and pick a minimal bottom one for consistency with a
// taskbar rounded corners.
- int getRoundedCornersRadius(WindowState mainWindow) {
- if (!requiresRoundedCorners(mainWindow)) {
+ int getRoundedCornersRadius(final WindowState mainWindow) {
+ if (!requiresRoundedCorners(mainWindow) || mActivityRecord.isInLetterboxAnimation()) {
return 0;
}
+ final int radius;
if (mLetterboxConfiguration.getLetterboxActivityCornersRadius() >= 0) {
- return mLetterboxConfiguration.getLetterboxActivityCornersRadius();
+ radius = mLetterboxConfiguration.getLetterboxActivityCornersRadius();
+ } else {
+ final InsetsState insetsState = mainWindow.getInsetsState();
+ radius = Math.min(
+ getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
+ getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
}
- final InsetsState insetsState = mainWindow.getInsetsState();
- return Math.min(
- getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_LEFT),
- getInsetsStateCornerRadius(insetsState, RoundedCorner.POSITION_BOTTOM_RIGHT));
+ final float scale = mainWindow.mInvGlobalScale;
+ return (scale != 1f && scale > 0f) ? (int) (scale * radius) : radius;
}
/**
- * Returns whether the taskbar is visible. Returns false if the window is in immersive mode,
- * since the user can swipe to show/hide the taskbar as an overlay.
+ * Returns the taskbar in case it is visible and expanded in height, otherwise returns null.
*/
- private boolean hasVisibleTaskbar(WindowState mainWindow) {
- final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow);
-
- return taskbarInsetsSource != null
- && taskbarInsetsSource.isVisible();
+ @VisibleForTesting
+ @Nullable
+ InsetsSource getExpandedTaskbarOrNull(final WindowState mainWindow) {
+ final InsetsSource taskbar = mainWindow.getInsetsState().peekSource(
+ InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ if (taskbar != null && taskbar.isVisible()
+ && taskbar.getFrame().height() >= mExpandedTaskBarHeight) {
+ return taskbar;
+ }
+ return null;
}
- private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) {
- final InsetsState insetsState = mainWindow.getInsetsState();
- return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- }
-
- private void adjustBoundsForTaskbar(WindowState mainWindow, Rect bounds) {
+ private void adjustBoundsIfNeeded(final WindowState mainWindow, final Rect bounds) {
// Rounded corners should be displayed above the taskbar. When taskbar is hidden,
// an insets frame is equal to a navigation bar which shouldn't affect position of
// rounded corners since apps are expected to handle navigation bar inset.
// This condition checks whether the taskbar is visible.
// Do not crop the taskbar inset if the window is in immersive mode - the user can
// swipe to show/hide the taskbar as an overlay.
- if (hasVisibleTaskbar(mainWindow)) {
- adjustBoundsForTaskbarUnchecked(mainWindow, bounds);
+ // Adjust the bounds only in case there is an expanded taskbar,
+ // otherwise the rounded corners will be shown behind the navbar.
+ final InsetsSource expandedTaskbarOrNull = getExpandedTaskbarOrNull(mainWindow);
+ if (expandedTaskbarOrNull != null) {
+ // Rounded corners should be displayed above the expanded taskbar.
+ bounds.bottom = Math.min(bounds.bottom, expandedTaskbarOrNull.getFrame().top);
}
- }
- private void adjustBoundsForTaskbarUnchecked(WindowState mainWindow, Rect bounds) {
- // Rounded corners should be displayed above the taskbar.
- bounds.bottom =
- Math.min(bounds.bottom, getTaskbarInsetsSource(mainWindow).getFrame().top);
- scaleIfNeeded(bounds);
+ final float scale = mainWindow.mInvGlobalScale;
+ if (scale != 1f && scale > 0f) {
+ bounds.scale(scale);
+ }
}
private int getInsetsStateCornerRadius(
@@ -1269,20 +1279,4 @@
mInheritedSizeCompatScale = 1f;
mInheritedCompatDisplayInsets = null;
}
-
- private void scaleIfNeeded(Rect bounds) {
- if (boundsNeedToScale()) {
- bounds.scale(1.0f / mActivityRecord.getCompatScale());
- }
- }
-
- private boolean boundsNeedToScale() {
- if (hasInheritedLetterboxBehavior()) {
- return mIsInheritedInSizeCompatMode
- && mInheritedSizeCompatScale < 1.0f;
- } else {
- return mActivityRecord.inSizeCompatMode()
- && mActivityRecord.getCompatScale() < 1.0f;
- }
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 5e087f0..478bd85 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -28,21 +28,36 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
+import android.annotation.Nullable;
import android.compat.testing.PlatformCompatChangeRule;
import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.Property;
+import android.content.res.Resources;
+import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.view.InsetsSource;
+import android.view.InsetsState;
+import android.view.RoundedCorner;
+import android.view.RoundedCorners;
+import android.view.WindowManager;
import androidx.test.filters.SmallTest;
+import com.android.internal.R;
+
import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
import org.junit.Before;
@@ -61,6 +76,14 @@
@Presubmit
@RunWith(WindowTestRunner.class)
public class LetterboxUiControllerTest extends WindowTestsBase {
+ private static final int TASKBAR_COLLAPSED_HEIGHT = 10;
+ private static final int TASKBAR_EXPANDED_HEIGHT = 20;
+ private static final int SCREEN_WIDTH = 200;
+ private static final int SCREEN_HEIGHT = 100;
+ private static final Rect TASKBAR_COLLAPSED_BOUNDS = new Rect(0,
+ SCREEN_HEIGHT - TASKBAR_COLLAPSED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
+ private static final Rect TASKBAR_EXPANDED_BOUNDS = new Rect(0,
+ SCREEN_HEIGHT - TASKBAR_EXPANDED_HEIGHT, SCREEN_WIDTH, SCREEN_HEIGHT);
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -69,6 +92,7 @@
private DisplayContent mDisplayContent;
private LetterboxUiController mController;
private LetterboxConfiguration mLetterboxConfiguration;
+ private final Rect mLetterboxedPortraitTaskBounds = new Rect();
@Before
public void setUp() throws Exception {
@@ -308,6 +332,162 @@
assertTrue(mController.shouldForceRotateForCameraCompat());
}
+ @Test
+ public void testGetCropBoundsIfNeeded_noCrop() {
+ final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+
+ // Do not apply crop if taskbar is collapsed
+ taskbar.setFrame(TASKBAR_COLLAPSED_BOUNDS);
+ assertNull(mController.getExpandedTaskbarOrNull(mainWindow));
+
+ mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, SCREEN_HEIGHT / 4,
+ SCREEN_WIDTH - SCREEN_WIDTH / 4, SCREEN_HEIGHT - SCREEN_HEIGHT / 4);
+
+ final Rect noCrop = mController.getCropBoundsIfNeeded(mainWindow);
+ assertNotEquals(null, noCrop);
+ assertEquals(0, noCrop.left);
+ assertEquals(0, noCrop.top);
+ assertEquals(mLetterboxedPortraitTaskBounds.width(), noCrop.right);
+ assertEquals(mLetterboxedPortraitTaskBounds.height(), noCrop.bottom);
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_appliesCrop() {
+ final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+
+ // Apply crop if taskbar is expanded
+ taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+ assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+
+ mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
+ SCREEN_HEIGHT);
+
+ final Rect crop = mController.getCropBoundsIfNeeded(mainWindow);
+ assertNotEquals(null, crop);
+ assertEquals(0, crop.left);
+ assertEquals(0, crop.top);
+ assertEquals(mLetterboxedPortraitTaskBounds.width(), crop.right);
+ assertEquals(mLetterboxedPortraitTaskBounds.height() - TASKBAR_EXPANDED_HEIGHT,
+ crop.bottom);
+ }
+
+ @Test
+ public void testGetCropBoundsIfNeeded_appliesCropWithSizeCompatScaling() {
+ final InsetsSource taskbar = new InsetsSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(taskbar);
+ final float scaling = 2.0f;
+
+ // Apply crop if taskbar is expanded
+ taskbar.setFrame(TASKBAR_EXPANDED_BOUNDS);
+ assertNotNull(mController.getExpandedTaskbarOrNull(mainWindow));
+ // With SizeCompat scaling
+ doReturn(true).when(mActivity).inSizeCompatMode();
+ mainWindow.mInvGlobalScale = scaling;
+
+ mLetterboxedPortraitTaskBounds.set(SCREEN_WIDTH / 4, 0, SCREEN_WIDTH - SCREEN_WIDTH / 4,
+ SCREEN_HEIGHT);
+
+ final int appWidth = mLetterboxedPortraitTaskBounds.width();
+ final int appHeight = mLetterboxedPortraitTaskBounds.height();
+
+ final Rect crop = mController.getCropBoundsIfNeeded(mainWindow);
+ assertNotEquals(null, crop);
+ assertEquals(0, crop.left);
+ assertEquals(0, crop.top);
+ assertEquals((int) (appWidth * scaling), crop.right);
+ assertEquals((int) ((appHeight - TASKBAR_EXPANDED_HEIGHT) * scaling), crop.bottom);
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_withRoundedCornersFromInsets() {
+ final float invGlobalScale = 0.5f;
+ final int expectedRadius = 7;
+ final int configurationRadius = 15;
+
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+ mainWindow.mInvGlobalScale = invGlobalScale;
+ final InsetsState insets = mainWindow.getInsetsState();
+
+ RoundedCorners roundedCorners = new RoundedCorners(
+ /*topLeft=*/ null,
+ /*topRight=*/ null,
+ /*bottomRight=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT,
+ configurationRadius, /*centerX=*/ 1, /*centerY=*/ 1),
+ /*bottomLeft=*/ new RoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT,
+ configurationRadius * 2 /*2 is to test selection of the min radius*/,
+ /*centerX=*/ 1, /*centerY=*/ 1)
+ );
+ doReturn(roundedCorners).when(insets).getRoundedCorners();
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(-1);
+
+ assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_withLetterboxActivityCornersRadius() {
+ final float invGlobalScale = 0.5f;
+ final int expectedRadius = 7;
+ final int configurationRadius = 15;
+
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+ mainWindow.mInvGlobalScale = invGlobalScale;
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+
+ assertEquals(expectedRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ }
+
+ @Test
+ public void testGetRoundedCornersRadius_noScalingApplied() {
+ final int configurationRadius = 15;
+
+ final WindowState mainWindow = mockForGetCropBoundsAndRoundedCorners(/*taskbar=*/ null);
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(configurationRadius);
+
+ mainWindow.mInvGlobalScale = -1f;
+ assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ mainWindow.mInvGlobalScale = 0f;
+ assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+
+ mainWindow.mInvGlobalScale = 1f;
+ assertEquals(configurationRadius, mController.getRoundedCornersRadius(mainWindow));
+ }
+
+ private WindowState mockForGetCropBoundsAndRoundedCorners(@Nullable InsetsSource taskbar) {
+ final WindowState mainWindow = mock(WindowState.class);
+ final InsetsState insets = mock(InsetsState.class);
+ final Resources resources = mWm.mContext.getResources();
+ final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams();
+
+ mainWindow.mInvGlobalScale = 1f;
+ spyOn(resources);
+ spyOn(mActivity);
+
+ if (taskbar != null) {
+ taskbar.setVisible(true);
+ doReturn(taskbar).when(insets).peekSource(taskbar.getType());
+ }
+ doReturn(mLetterboxedPortraitTaskBounds).when(mActivity).getBounds();
+ doReturn(true).when(mActivity).isVisible();
+ doReturn(true).when(mActivity).isLetterboxedForFixedOrientationAndAspectRatio();
+ doReturn(insets).when(mainWindow).getInsetsState();
+ doReturn(attrs).when(mainWindow).getAttrs();
+ doReturn(true).when(mainWindow).isDrawn();
+ doReturn(false).when(mainWindow).isLetterboxedForDisplayCutout();
+ doReturn(true).when(mainWindow).areAppWindowBoundsLetterboxed();
+ doReturn(true).when(mLetterboxConfiguration).isLetterboxActivityCornersRounded();
+ doReturn(TASKBAR_EXPANDED_HEIGHT).when(resources).getDimensionPixelSize(
+ R.dimen.taskbar_frame_height);
+
+ // Need to reinitialise due to the change in resources getDimensionPixelSize output.
+ mController = new LetterboxUiController(mWm, mActivity);
+
+ return mainWindow;
+ }
+
private void mockThatProperty(String propertyName, boolean value) throws Exception {
Property property = new Property(propertyName, /* value */ value, /* packageName */ "",
/* className */ "");
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 de0999f..46d69d1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2821,7 +2821,7 @@
mActivity.mRootWindowContainer.performSurfacePlacement();
final ArgumentCaptor<Rect> cropCapturer = ArgumentCaptor.forClass(Rect.class);
- verify(mTransaction, times(2)).setWindowCrop(
+ verify(mTransaction, times(2)).setCrop(
eq(w1.getSurfaceControl()),
cropCapturer.capture()
);