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

Commit 24264214 authored by Adrian Roos's avatar Adrian Roos
Browse files

DisplayCutout: Dispatch all non-zero safeInsets

Ensures that even if the display cutout does not overlap
a window, the safe insets are still dispatched if the window
overlaps any part of the cutout area and the safe insets
are therefore non-zero.

To do that, we need to compute the insets once against the
display size, then only shrink or expand the safe insets
accordingly.

Bug: 73533636
Test: atest DisplayCutoutTest
Change-Id: I1ea449178e29c7effdd92cac78af1a3875ca7e70
parent 944836c6
Loading
Loading
Loading
Loading
+64 −41
Original line number Diff line number Diff line
@@ -26,7 +26,6 @@ import static android.view.Surface.ROTATION_90;
import android.content.res.Resources;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
@@ -35,12 +34,13 @@ import android.os.Parcelable;
import android.text.TextUtils;
import android.util.Log;
import android.util.PathParser;
import android.util.Size;
import android.util.proto.ProtoOutputStream;

import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;

import java.util.List;
import java.util.Objects;

/**
 * Represents a part of the display that is not functional for displaying content.
@@ -70,10 +70,12 @@ public final class DisplayCutout {
     *
     * @hide
     */
    public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION);
    public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION,
            new Size(0, 0));

    private final Rect mSafeInsets;
    private final Region mBounds;
    private final Size mFrameSize;

    /**
     * Creates a DisplayCutout instance.
@@ -83,9 +85,10 @@ public final class DisplayCutout {
     * @hide
     */
    @VisibleForTesting
    public DisplayCutout(Rect safeInsets, Region bounds) {
    public DisplayCutout(Rect safeInsets, Region bounds, Size frameSize) {
        mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
        mBounds = bounds != null ? bounds : Region.obtain();
        mFrameSize = frameSize;
    }

    /**
@@ -164,7 +167,8 @@ public final class DisplayCutout {
        if (o instanceof DisplayCutout) {
            DisplayCutout c = (DisplayCutout) o;
            return mSafeInsets.equals(c.mSafeInsets)
                    && mBounds.equals(c.mBounds);
                    && mBounds.equals(c.mBounds)
                    && Objects.equals(mFrameSize, c.mFrameSize);
        }
        return false;
    }
@@ -217,70 +221,84 @@ public final class DisplayCutout {
        }

        bounds.translate(-insetLeft, -insetTop);
        Size frame = mFrameSize == null ? null : new Size(
                mFrameSize.getWidth() - insetLeft - insetRight,
                mFrameSize.getHeight() - insetTop - insetBottom);

        return new DisplayCutout(safeInsets, bounds);
        return new DisplayCutout(safeInsets, bounds, frame);
    }

    /**
     * Calculates the safe insets relative to the given reference frame.
     * Recalculates the cutout relative to the given reference frame.
     *
     * @return a copy of this instance with the safe insets calculated
     * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}.
     *
     * @return a copy of this instance with the safe insets recalculated
     * @hide
     */
    public DisplayCutout calculateRelativeTo(Rect frame) {
        if (mBounds.isEmpty() || !Rect.intersects(frame, mBounds.getBounds())) {
        return inset(frame.left, frame.top,
                mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom);
    }

    /**
     * Calculates the safe insets relative to the given display size.
     *
     * @return a copy of this instance with the safe insets calculated
     * @hide
     */
    public DisplayCutout computeSafeInsets(int width, int height) {
        if (this == NO_CUTOUT || mBounds.isEmpty()) {
            return NO_CUTOUT;
        }

        return DisplayCutout.calculateRelativeTo(frame, Region.obtain(mBounds));
        return computeSafeInsets(new Size(width, height), mBounds);
    }

    private static DisplayCutout calculateRelativeTo(Rect frame, Region bounds) {
    private static DisplayCutout computeSafeInsets(Size displaySize, Region bounds) {
        Rect boundingRect = bounds.getBounds();
        Rect safeRect = new Rect();

        int bestArea = 0;
        int bestVariant = 0;
        for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
            int area = calculateInsetVariantArea(frame, boundingRect, variant, safeRect);
            int area = calculateInsetVariantArea(displaySize, boundingRect, variant, safeRect);
            if (bestArea < area) {
                bestArea = area;
                bestVariant = variant;
            }
        }
        calculateInsetVariantArea(frame, boundingRect, bestVariant, safeRect);
        calculateInsetVariantArea(displaySize, boundingRect, bestVariant, safeRect);
        if (safeRect.isEmpty()) {
            // The entire frame overlaps with the cutout.
            safeRect.set(0, frame.height(), 0, 0);
            // The entire displaySize overlaps with the cutout.
            safeRect.set(0, displaySize.getHeight(), 0, 0);
        } else {
            // Convert safeRect to insets relative to frame. We're reusing the rect here to avoid
            // an allocation.
            // Convert safeRect to insets relative to displaySize. We're reusing the rect here to
            // avoid an allocation.
            safeRect.set(
                    Math.max(0, safeRect.left - frame.left),
                    Math.max(0, safeRect.top - frame.top),
                    Math.max(0, frame.right - safeRect.right),
                    Math.max(0, frame.bottom - safeRect.bottom));
                    Math.max(0, safeRect.left),
                    Math.max(0, safeRect.top),
                    Math.max(0, displaySize.getWidth() - safeRect.right),
                    Math.max(0, displaySize.getHeight() - safeRect.bottom));
        }

        bounds.translate(-frame.left, -frame.top);

        return new DisplayCutout(safeRect, bounds);
        return new DisplayCutout(safeRect, bounds, displaySize);
    }

    private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
    private static int calculateInsetVariantArea(Size display, Rect boundingRect, int variant,
            Rect outSafeRect) {
        switch (variant) {
            case ROTATION_0:
                outSafeRect.set(frame.left, frame.top, frame.right, boundingRect.top);
                outSafeRect.set(0, 0, display.getWidth(), boundingRect.top);
                break;
            case ROTATION_90:
                outSafeRect.set(frame.left, frame.top, boundingRect.left, frame.bottom);
                outSafeRect.set(0, 0, boundingRect.left, display.getHeight());
                break;
            case ROTATION_180:
                outSafeRect.set(frame.left, boundingRect.bottom, frame.right, frame.bottom);
                outSafeRect.set(0, boundingRect.bottom, display.getWidth(), display.getHeight());
                break;
            case ROTATION_270:
                outSafeRect.set(boundingRect.right, frame.top, frame.right, frame.bottom);
                outSafeRect.set(boundingRect.right, 0, display.getWidth(), display.getHeight());
                break;
        }

@@ -293,21 +311,17 @@ public final class DisplayCutout {


    /**
     * Creates an instance from a bounding polygon.
     * Creates an instance from a bounding rect.
     *
     * @hide
     */
    public static DisplayCutout fromBoundingPolygon(List<Point> points) {
    public static DisplayCutout fromBoundingRect(int left, int top, int right, int bottom) {
        Path path = new Path();
        path.reset();
        for (int i = 0; i < points.size(); i++) {
            Point point = points.get(i);
            if (i == 0) {
                path.moveTo(point.x, point.y);
            } else {
                path.lineTo(point.x, point.y);
            }
        }
        path.moveTo(left, top);
        path.lineTo(left, bottom);
        path.lineTo(right, bottom);
        path.lineTo(right, top);
        path.close();
        return fromBounds(path);
    }
@@ -327,7 +341,7 @@ public final class DisplayCutout {
        Region bounds = new Region();
        bounds.setPath(path, clipRegion);
        clipRegion.recycle();
        return new DisplayCutout(ZERO_RECT, bounds);
        return new DisplayCutout(ZERO_RECT, bounds, null /* frameSize */);
    }

    /**
@@ -407,6 +421,12 @@ public final class DisplayCutout {
                out.writeInt(1);
                out.writeTypedObject(cutout.mSafeInsets, flags);
                out.writeTypedObject(cutout.mBounds, flags);
                if (cutout.mFrameSize != null) {
                    out.writeInt(cutout.mFrameSize.getWidth());
                    out.writeInt(cutout.mFrameSize.getHeight());
                } else {
                    out.writeInt(-1);
                }
            }
        }

@@ -449,7 +469,10 @@ public final class DisplayCutout {
            Rect safeInsets = in.readTypedObject(Rect.CREATOR);
            Region bounds = in.readTypedObject(Region.CREATOR);

            return new DisplayCutout(safeInsets, bounds);
            int width = in.readInt();
            Size frameSize = width >= 0 ? new Size(width, in.readInt()) : null;

            return new DisplayCutout(safeInsets, bounds, frameSize);
        }

        public DisplayCutout get() {
+78 −21
Original line number Diff line number Diff line
@@ -17,26 +17,25 @@
package android.view;

import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.DisplayCutout.fromBoundingRect;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import android.util.Size;
import android.view.DisplayCutout.ParcelableWrapper;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Arrays;

@RunWith(AndroidJUnit4.class)
@SmallTest
@Presubmit
@@ -45,7 +44,7 @@ public class DisplayCutoutTest {
    /** This is not a consistent cutout. Useful for verifying insets in one go though. */
    final DisplayCutout mCutoutNumbers = new DisplayCutout(
            new Rect(1, 2, 3, 4),
            new Region(5, 6, 7, 8));
            new Region(5, 6, 7, 8), new Size(9, 10));

    final DisplayCutout mCutoutTop = createCutoutTop();

@@ -154,51 +153,95 @@ public class DisplayCutoutTest {
        assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect());
    }

    @Test
    public void computeSafeInsets_top() throws Exception {
        DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20)
                .computeSafeInsets(200, 400);

        assertEquals(new Rect(0, 20, 0, 0), cutout.getSafeInsets());
    }

    @Test
    public void computeSafeInsets_left() throws Exception {
        DisplayCutout cutout = fromBoundingRect(0, 0, 20, 100)
                .computeSafeInsets(400, 200);

        assertEquals(new Rect(20, 0, 0, 0), cutout.getSafeInsets());
    }

    @Test
    public void computeSafeInsets_bottom() throws Exception {
        DisplayCutout cutout = fromBoundingRect(0, 180, 100, 200)
                .computeSafeInsets(100, 200);

        assertEquals(new Rect(0, 0, 0, 20), cutout.getSafeInsets());
    }

    @Test
    public void computeSafeInsets_right() throws Exception {
        DisplayCutout cutout = fromBoundingRect(180, 0, 200, 100)
                .computeSafeInsets(200, 100);

        assertEquals(new Rect(0, 0, 20, 0), cutout.getSafeInsets());
    }

    @Test
    public void computeSafeInsets_bounds() throws Exception {
        DisplayCutout cutout = mCutoutTop.computeSafeInsets(1000, 2000);

        assertEquals(mCutoutTop.getBoundingRect(), cutout.getBounds().getBounds());
    }

    @Test
    public void calculateRelativeTo_top() throws Exception {
        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400));
        DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20)
                .computeSafeInsets(200, 400)
                .calculateRelativeTo(new Rect(5, 5, 95, 195));

        assertEquals(new Rect(0, 100, 0, 0), cutout.getSafeInsets());
        assertEquals(new Rect(0, 15, 0, 0), cutout.getSafeInsets());
    }

    @Test
    public void calculateRelativeTo_left() throws Exception {
        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200));
        DisplayCutout cutout = fromBoundingRect(0, 0, 20, 100)
                .computeSafeInsets(400, 200)
                .calculateRelativeTo(new Rect(5, 5, 195, 95));

        assertEquals(new Rect(75, 0, 0, 0), cutout.getSafeInsets());
        assertEquals(new Rect(15, 0, 0, 0), cutout.getSafeInsets());
    }

    @Test
    public void calculateRelativeTo_bottom() throws Exception {
        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100));
        DisplayCutout cutout = fromBoundingRect(0, 180, 100, 200)
                .computeSafeInsets(100, 200)
                .calculateRelativeTo(new Rect(5, 5, 95, 195));

        assertEquals(new Rect(0, 0, 0, 100), cutout.getSafeInsets());
        assertEquals(new Rect(0, 0, 0, 15), cutout.getSafeInsets());
    }

    @Test
    public void calculateRelativeTo_right() throws Exception {
        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100));
        DisplayCutout cutout = fromBoundingRect(180, 0, 200, 100)
                .computeSafeInsets(200, 100)
                .calculateRelativeTo(new Rect(5, 5, 195, 95));

        assertEquals(new Rect(0, 0, 50, 0), cutout.getSafeInsets());
        assertEquals(new Rect(0, 0, 15, 0), cutout.getSafeInsets());
    }

    @Test
    public void calculateRelativeTo_bounds() throws Exception {
        DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200));
        DisplayCutout cutout = fromBoundingRect(0, 0, 100, 20)
                .computeSafeInsets(200, 400)
                .calculateRelativeTo(new Rect(5, 10, 95, 180));

        assertEquals(new Rect(1050, 2000, 1075, 2100), cutout.getBoundingRect());
        assertEquals(new Rect(-5, -10, 95, 10), cutout.getBounds().getBounds());
    }

    @Test
    public void fromBoundingPolygon() throws Exception {
        assertEquals(
                new Rect(50, 0, 75, 100),
                DisplayCutout.fromBoundingPolygon(
                        Arrays.asList(
                                new Point(75, 0),
                                new Point(50, 0),
                                new Point(75, 100),
                                new Point(50, 100))).getBounds().getBounds());
                DisplayCutout.fromBoundingRect(50, 0, 75, 100).getBounds().getBounds());
    }

    @Test
@@ -214,6 +257,19 @@ public class DisplayCutoutTest {
        assertEquals(posAfterWrite, p.dataPosition());
    }

    @Test
    public void parcel_unparcel_withFrame() {
        Parcel p = Parcel.obtain();

        new ParcelableWrapper(mCutoutNumbers).writeToParcel(p, 0);
        int posAfterWrite = p.dataPosition();

        p.setDataPosition(0);

        assertEquals(mCutoutNumbers, ParcelableWrapper.CREATOR.createFromParcel(p).get());
        assertEquals(posAfterWrite, p.dataPosition());
    }

    @Test
    public void parcel_unparcel_nocutout() {
        Parcel p = Parcel.obtain();
@@ -264,6 +320,7 @@ public class DisplayCutoutTest {
    private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
        return new DisplayCutout(
                new Rect(left, top, right, bottom),
                new Region(50, 0, 75, 100));
                new Region(50, 0, 75, 100),
                null);
    }
}
+2 −3
Original line number Diff line number Diff line
@@ -387,11 +387,10 @@ public class ScreenDecorations extends SystemUI implements Tunable {
        }

        private boolean hasCutout() {
            if (mInfo.displayCutout == null) {
            final DisplayCutout displayCutout = mInfo.displayCutout;
            if (displayCutout == null) {
                return false;
            }
            DisplayCutout displayCutout = mInfo.displayCutout.calculateRelativeTo(
                    new Rect(0, 0, mInfo.logicalWidth, mInfo.logicalHeight));
            if (mStart) {
                return displayCutout.getSafeInsetLeft() > 0
                        || displayCutout.getSafeInsetTop() > 0;
+9 −2
Original line number Diff line number Diff line
@@ -138,6 +138,7 @@ import android.os.Trace;
import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.MutableBoolean;
import android.util.Size;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -1197,14 +1198,20 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo

    DisplayCutout calculateDisplayCutoutForCurrentRotation() {
        final DisplayCutout cutout = mInitialDisplayCutout;
        if (cutout == null || cutout == DisplayCutout.NO_CUTOUT || mRotation == ROTATION_0) {
        if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
            return cutout;
        }
        if (mRotation == ROTATION_0) {
            return cutout.computeSafeInsets(mInitialDisplayWidth, mInitialDisplayHeight);
        }
        final boolean rotated = (mRotation == ROTATION_90 || mRotation == ROTATION_270);
        final Path bounds = cutout.getBounds().getBoundaryPath();
        transformPhysicalToLogicalCoordinates(mRotation, mInitialDisplayWidth,
                mInitialDisplayHeight, mTmpMatrix);
        bounds.transform(mTmpMatrix);
        return DisplayCutout.fromBounds(bounds);
        return DisplayCutout.fromBounds(bounds).computeSafeInsets(
                rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
                rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
    }

    /**
+1 −6
Original line number Diff line number Diff line
@@ -22,17 +22,12 @@ import static android.view.Surface.ROTATION_90;
import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;

import android.annotation.NonNull;
import android.content.res.Resources;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayCutout;
import android.view.DisplayInfo;

import com.android.internal.annotations.VisibleForTesting;

import java.io.PrintWriter;
import java.util.Arrays;

/**
 * Container class for all the display frames that affect how we do window layout on a display.
@@ -173,7 +168,7 @@ public class DisplayFrames {
        mStableFullscreen.set(mUnrestricted);
        mCurrent.set(mUnrestricted);

        mDisplayCutout = mDisplayInfoCutout.calculateRelativeTo(mOverscan);
        mDisplayCutout = mDisplayInfoCutout;
        mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
                Integer.MAX_VALUE, Integer.MAX_VALUE);
        if (!mDisplayCutout.isEmpty()) {
Loading