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

Commit b1491547 authored by Mariia Sandrikova's avatar Mariia Sandrikova Committed by Automerger Merge Worker
Browse files

Merge "Show letterbox rounded corners behind navbar" into tm-qpr-dev am: 6f20d51b

parents 158bd35c 6f20d51b
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()
        );