Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6f20d51b authored by Mariia Sandrikova's avatar Mariia Sandrikova Committed by Android (Google) Code Review
Browse files

Merge "Show letterbox rounded corners behind navbar" into tm-qpr-dev

parents 87f4c66c 4b843911
Loading
Loading
Loading
Loading
+80 −86
Original line number Diff line number Diff line
@@ -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
@@ -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);
    }

    /**
@@ -422,7 +431,7 @@ final class LetterboxUiController {
            if (w == null) {
                return;
            }
            adjustBoundsForTaskbar(w, outBounds);
            adjustBoundsIfNeeded(w, outBounds);
        } else {
            outBounds.setEmpty();
        }
@@ -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);
@@ -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
@@ -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(
@@ -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;
        }
    }
}
+180 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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();
@@ -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 {
@@ -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 */ "");
+1 −1
Original line number Diff line number Diff line
@@ -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()
        );