Loading services/core/java/com/android/server/wm/LetterboxUiController.java +80 −86 Original line number Diff line number Diff line Loading @@ -105,6 +105,12 @@ final class LetterboxUiController { 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 Loading Loading @@ -184,6 +190,9 @@ final class LetterboxUiController { () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); mExpandedTaskBarHeight = getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); } /** Loading Loading @@ -422,7 +431,7 @@ final class LetterboxUiController { if (w == null) { return; } adjustBoundsForTaskbar(w, outBounds); adjustBoundsIfNeeded(w, outBounds); } else { outBounds.setEmpty(); } Loading Loading @@ -465,13 +474,13 @@ final class LetterboxUiController { 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); Loading Loading @@ -775,8 +784,8 @@ final class LetterboxUiController { 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 Loading Loading @@ -828,106 +837,107 @@ final class LetterboxUiController { 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 (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)); } @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. transaction .setWindowCrop(windowSurface, null) .setCornerRadius(windowSurface, 0); return; return null; } Rect cropBounds = null; final Rect cropBounds = new Rect(mActivityRecord.getBounds()); if (hasVisibleTaskbar(mainWindow)) { 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); // 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 // 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; } transaction .setWindowCrop(windowSurface, cropBounds) .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); } } private boolean requiresRoundedCorners(WindowState mainWindow) { final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); 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(); return Math.min( radius = 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; } private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) { final InsetsState insetsState = mainWindow.getInsetsState(); return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); return null; } 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( Loading Loading @@ -1269,20 +1279,4 @@ final class LetterboxUiController { 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; } } } services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +180 −0 Original line number Diff line number Diff line Loading @@ -28,21 +28,36 @@ import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENT 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; Loading @@ -61,6 +76,14 @@ import org.junit.runner.RunWith; @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(); Loading @@ -69,6 +92,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private DisplayContent mDisplayContent; private LetterboxUiController mController; private LetterboxConfiguration mLetterboxConfiguration; private final Rect mLetterboxedPortraitTaskBounds = new Rect(); @Before public void setUp() throws Exception { Loading Loading @@ -308,6 +332,162 @@ public class LetterboxUiControllerTest extends WindowTestsBase { 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 */ ""); Loading services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +1 −1 Original line number Diff line number Diff line Loading @@ -2821,7 +2821,7 @@ public class SizeCompatTests extends WindowTestsBase { 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() ); Loading Loading
services/core/java/com/android/server/wm/LetterboxUiController.java +80 −86 Original line number Diff line number Diff line Loading @@ -105,6 +105,12 @@ final class LetterboxUiController { 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 Loading Loading @@ -184,6 +190,9 @@ final class LetterboxUiController { () -> mLetterboxConfiguration.isCameraCompatTreatmentEnabled( /* checkDeviceConfig */ true), PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE); mExpandedTaskBarHeight = getResources().getDimensionPixelSize(R.dimen.taskbar_frame_height); } /** Loading Loading @@ -422,7 +431,7 @@ final class LetterboxUiController { if (w == null) { return; } adjustBoundsForTaskbar(w, outBounds); adjustBoundsIfNeeded(w, outBounds); } else { outBounds.setEmpty(); } Loading Loading @@ -465,13 +474,13 @@ final class LetterboxUiController { 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); Loading Loading @@ -775,8 +784,8 @@ final class LetterboxUiController { 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 Loading Loading @@ -828,106 +837,107 @@ final class LetterboxUiController { 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 (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)); } @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. transaction .setWindowCrop(windowSurface, null) .setCornerRadius(windowSurface, 0); return; return null; } Rect cropBounds = null; final Rect cropBounds = new Rect(mActivityRecord.getBounds()); if (hasVisibleTaskbar(mainWindow)) { 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); // 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 // 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; } transaction .setWindowCrop(windowSurface, cropBounds) .setCornerRadius(windowSurface, getRoundedCornersRadius(mainWindow)); } } private boolean requiresRoundedCorners(WindowState mainWindow) { final InsetsSource taskbarInsetsSource = getTaskbarInsetsSource(mainWindow); 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(); return Math.min( radius = 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; } private InsetsSource getTaskbarInsetsSource(WindowState mainWindow) { final InsetsState insetsState = mainWindow.getInsetsState(); return insetsState.peekSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR); return null; } 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( Loading Loading @@ -1269,20 +1279,4 @@ final class LetterboxUiController { 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; } } }
services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +180 −0 Original line number Diff line number Diff line Loading @@ -28,21 +28,36 @@ import static android.view.WindowManager.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENT 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; Loading @@ -61,6 +76,14 @@ import org.junit.runner.RunWith; @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(); Loading @@ -69,6 +92,7 @@ public class LetterboxUiControllerTest extends WindowTestsBase { private DisplayContent mDisplayContent; private LetterboxUiController mController; private LetterboxConfiguration mLetterboxConfiguration; private final Rect mLetterboxedPortraitTaskBounds = new Rect(); @Before public void setUp() throws Exception { Loading Loading @@ -308,6 +332,162 @@ public class LetterboxUiControllerTest extends WindowTestsBase { 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 */ ""); Loading
services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +1 −1 Original line number Diff line number Diff line Loading @@ -2821,7 +2821,7 @@ public class SizeCompatTests extends WindowTestsBase { 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() ); Loading