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

Commit 61121757 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Display Cutout: Add Cutout to WindowInsets"

parents 14e1b283 d4970af1
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -889,7 +889,8 @@ public abstract class WallpaperService extends Service {
                            mFinalStableInsets.set(mDispatchedStableInsets);
                            WindowInsets insets = new WindowInsets(mFinalSystemInsets,
                                    null, mFinalStableInsets,
                                    getResources().getConfiguration().isScreenRound(), false);
                                    getResources().getConfiguration().isScreenRound(), false,
                                    null /* displayCutout */);
                            if (DEBUG) {
                                Log.v(TAG, "dispatching insets=" + insets);
                            }
+408 −0
Original line number Diff line number Diff line
/*
 * Copyright 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.view;

import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_180;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;

import android.annotation.NonNull;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.List;

/**
 * Represents a part of the display that is not functional for displaying content.
 *
 * <p>{@code DisplayCutout} is immutable.
 *
 * @hide will become API
 */
public final class DisplayCutout {

    private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
    private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();

    /**
     * An instance where {@link #hasCutout()} returns {@code false}.
     *
     * @hide
     */
    public static final DisplayCutout NO_CUTOUT =
            new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);

    private final Rect mSafeInsets;
    private final Rect mBoundingRect;
    private final List<Point> mBoundingPolygon;

    /**
     * Creates a DisplayCutout instance.
     *
     * NOTE: the Rects passed into this instance are not copied and MUST remain unchanged.
     *
     * @hide
     */
    @VisibleForTesting
    public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
        mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
        mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
        mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
    }

    /**
     * Returns whether there is a cutout.
     *
     * If false, the safe insets will all return zero, and the bounding box or polygon will be
     * empty or outside the content view.
     *
     * @return {@code true} if there is a cutout, {@code false} otherwise
     */
    public boolean hasCutout() {
        return !mSafeInsets.equals(ZERO_RECT);
    }

    /** Returns the inset from the top which avoids the display cutout. */
    public int getSafeInsetTop() {
        return mSafeInsets.top;
    }

    /** Returns the inset from the bottom which avoids the display cutout. */
    public int getSafeInsetBottom() {
        return mSafeInsets.bottom;
    }

    /** Returns the inset from the left which avoids the display cutout. */
    public int getSafeInsetLeft() {
        return mSafeInsets.left;
    }

    /** Returns the inset from the right which avoids the display cutout. */
    public int getSafeInsetRight() {
        return mSafeInsets.right;
    }

    /**
     * Obtains the safe insets in a rect.
     *
     * @param out a rect which is set to the safe insets.
     * @hide
     */
    public void getSafeInsets(@NonNull Rect out) {
        out.set(mSafeInsets);
    }

    /**
     * Obtains the bounding rect of the cutout.
     *
     * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
     *         to the top-left corner of the content view.
     */
    public void getBoundingRect(@NonNull Rect outRect) {
        outRect.set(mBoundingRect);
    }

    /**
     * Obtains the bounding polygon of the cutout.
     *
     * @param outPolygon is filled with a list of points representing the corners of a convex
     *         polygon which covers the cutout. Coordinates are relative to the
     *         top-left corner of the content view.
     */
    public void getBoundingPolygon(List<Point> outPolygon) {
        outPolygon.clear();
        for (int i = 0; i < mBoundingPolygon.size(); i++) {
            outPolygon.add(new Point(mBoundingPolygon.get(i)));
        }
    }

    @Override
    public int hashCode() {
        int result = mSafeInsets.hashCode();
        result = result * 31 + mBoundingRect.hashCode();
        result = result * 31 + mBoundingPolygon.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
        if (o instanceof DisplayCutout) {
            DisplayCutout c = (DisplayCutout) o;
            return mSafeInsets.equals(c.mSafeInsets)
                    && mBoundingRect.equals(c.mBoundingRect)
                    && mBoundingPolygon.equals(c.mBoundingPolygon);
        }
        return false;
    }

    @Override
    public String toString() {
        return "DisplayCutout{insets=" + mSafeInsets
                + " bounding=" + mBoundingRect
                + "}";
    }

    /**
     * Insets the reference frame of the cutout in the given directions.
     *
     * @return a copy of this instance which has been inset
     * @hide
     */
    public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
        if (mBoundingRect.isEmpty()
                || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
            return this;
        }

        Rect safeInsets = new Rect(mSafeInsets);
        Rect boundingRect = new Rect(mBoundingRect);
        ArrayList<Point> boundingPolygon = new ArrayList<>();
        getBoundingPolygon(boundingPolygon);

        // Note: it's not really well defined what happens when the inset is negative, because we
        // don't know if the safe inset needs to expand in general.
        if (insetTop > 0 || safeInsets.top > 0) {
            safeInsets.top = atLeastZero(safeInsets.top - insetTop);
        }
        if (insetBottom > 0 || safeInsets.bottom > 0) {
            safeInsets.bottom = atLeastZero(safeInsets.bottom - insetBottom);
        }
        if (insetLeft > 0 || safeInsets.left > 0) {
            safeInsets.left = atLeastZero(safeInsets.left - insetLeft);
        }
        if (insetRight > 0 || safeInsets.right > 0) {
            safeInsets.right = atLeastZero(safeInsets.right - insetRight);
        }

        boundingRect.offset(-insetLeft, -insetTop);
        offset(boundingPolygon, -insetLeft, -insetTop);

        return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
    }

    /**
     * Calculates the safe insets relative to the given reference frame.
     *
     * @return a copy of this instance with the safe insets calculated
     * @hide
     */
    public DisplayCutout calculateRelativeTo(Rect frame) {
        if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
            return NO_CUTOUT;
        }

        Rect boundingRect = new Rect(mBoundingRect);
        ArrayList<Point> boundingPolygon = new ArrayList<>();
        getBoundingPolygon(boundingPolygon);

        return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
    }

    private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
            ArrayList<Point> boundingPolygon) {
        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);
            if (bestArea < area) {
                bestArea = area;
                bestVariant = variant;
            }
        }
        calculateInsetVariantArea(frame, boundingRect, bestVariant, safeRect);
        if (safeRect.isEmpty()) {
            // The entire frame overlaps with the cutout.
            safeRect.set(0, frame.height(), 0, 0);
        } else {
            // Convert safeRect to insets relative to frame. 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));
        }

        boundingRect.offset(-frame.left, -frame.top);
        offset(boundingPolygon, -frame.left, -frame.top);

        return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
    }

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

        return outSafeRect.isEmpty() ? 0 : outSafeRect.width() * outSafeRect.height();
    }

    private static int atLeastZero(int value) {
        return value < 0 ? 0 : value;
    }

    private static void offset(ArrayList<Point> points, int dx, int dy) {
        for (int i = 0; i < points.size(); i++) {
            points.get(i).offset(dx, dy);
        }
    }

    /**
     * Creates an instance from a bounding polygon.
     *
     * @hide
     */
    public static DisplayCutout fromBoundingPolygon(List<Point> points) {
        Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
                Integer.MIN_VALUE, Integer.MIN_VALUE);
        ArrayList<Point> boundingPolygon = new ArrayList<>();

        for (int i = 0; i < points.size(); i++) {
            Point point = points.get(i);
            boundingRect.left = Math.min(boundingRect.left, point.x);
            boundingRect.right = Math.max(boundingRect.right, point.x);
            boundingRect.top = Math.min(boundingRect.top, point.y);
            boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
            boundingPolygon.add(new Point(point));
        }

        return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
    }

    /**
     * Helper class for passing {@link DisplayCutout} through binder.
     *
     * Needed, because {@code readFromParcel} cannot be used with immutable classes.
     *
     * @hide
     */
    public static final class ParcelableWrapper implements Parcelable {

        private DisplayCutout mInner;

        public ParcelableWrapper() {
            this(NO_CUTOUT);
        }

        public ParcelableWrapper(DisplayCutout cutout) {
            mInner = cutout;
        }

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel out, int flags) {
            if (mInner == NO_CUTOUT) {
                out.writeInt(0);
            } else {
                out.writeInt(1);
                out.writeTypedObject(mInner.mSafeInsets, flags);
                out.writeTypedObject(mInner.mBoundingRect, flags);
                out.writeTypedList(mInner.mBoundingPolygon, flags);
            }
        }

        /**
         * Similar to {@link Creator#createFromParcel(Parcel)}, but reads into an existing
         * instance.
         *
         * Needed for AIDL out parameters.
         */
        public void readFromParcel(Parcel in) {
            mInner = readCutout(in);
        }

        public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() {
            @Override
            public ParcelableWrapper createFromParcel(Parcel in) {
                return new ParcelableWrapper(readCutout(in));
            }

            @Override
            public ParcelableWrapper[] newArray(int size) {
                return new ParcelableWrapper[size];
            }
        };

        private static DisplayCutout readCutout(Parcel in) {
            if (in.readInt() == 0) {
                return NO_CUTOUT;
            }

            ArrayList<Point> boundingPolygon = new ArrayList<>();

            Rect safeInsets = in.readTypedObject(Rect.CREATOR);
            Rect boundingRect = in.readTypedObject(Rect.CREATOR);
            in.readTypedList(boundingPolygon, Point.CREATOR);

            return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
        }

        public DisplayCutout get() {
            return mInner;
        }

        public void set(ParcelableWrapper cutout) {
            mInner = cutout.get();
        }

        public void set(DisplayCutout cutout) {
            mInner = cutout;
        }

        @Override
        public int hashCode() {
            return mInner.hashCode();
        }

        @Override
        public boolean equals(Object o) {
            return o instanceof ParcelableWrapper
                    && mInner.equals(((ParcelableWrapper) o).mInner);
        }

        @Override
        public String toString() {
            return String.valueOf(mInner);
        }
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -1563,7 +1563,7 @@ public final class ViewRootImpl implements ViewParent,
            mLastWindowInsets = new WindowInsets(contentInsets,
                    null /* windowDecorInsets */, stableInsets,
                    mContext.getResources().getConfiguration().isScreenRound(),
                    mAttachInfo.mAlwaysConsumeNavBar);
                    mAttachInfo.mAlwaysConsumeNavBar, null /* displayCutout */);
        }
        return mLastWindowInsets;
    }
+43 −7
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

package android.view;

import android.annotation.NonNull;
import android.graphics.Rect;

/**
@@ -36,6 +37,7 @@ public final class WindowInsets {
    private Rect mStableInsets;
    private Rect mTempRect;
    private boolean mIsRound;
    private DisplayCutout mDisplayCutout;

    /**
     * In multi-window we force show the navigation bar. Because we don't want that the surface size
@@ -47,6 +49,7 @@ public final class WindowInsets {
    private boolean mSystemWindowInsetsConsumed = false;
    private boolean mWindowDecorInsetsConsumed = false;
    private boolean mStableInsetsConsumed = false;
    private boolean mCutoutConsumed = false;

    private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);

@@ -59,12 +62,12 @@ public final class WindowInsets {
    public static final WindowInsets CONSUMED;

    static {
        CONSUMED = new WindowInsets(null, null, null, false, false);
        CONSUMED = new WindowInsets(null, null, null, false, false, null);
    }

    /** @hide */
    public WindowInsets(Rect systemWindowInsets, Rect windowDecorInsets, Rect stableInsets,
            boolean isRound, boolean alwaysConsumeNavBar) {
            boolean isRound, boolean alwaysConsumeNavBar, DisplayCutout displayCutout) {
        mSystemWindowInsetsConsumed = systemWindowInsets == null;
        mSystemWindowInsets = mSystemWindowInsetsConsumed ? EMPTY_RECT : systemWindowInsets;

@@ -76,6 +79,9 @@ public final class WindowInsets {

        mIsRound = isRound;
        mAlwaysConsumeNavBar = alwaysConsumeNavBar;

        mCutoutConsumed = displayCutout == null;
        mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
    }

    /**
@@ -92,11 +98,13 @@ public final class WindowInsets {
        mStableInsetsConsumed = src.mStableInsetsConsumed;
        mIsRound = src.mIsRound;
        mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
        mDisplayCutout = src.mDisplayCutout;
        mCutoutConsumed = src.mCutoutConsumed;
    }

    /** @hide */
    public WindowInsets(Rect systemWindowInsets) {
        this(systemWindowInsets, null, null, false, false);
        this(systemWindowInsets, null, null, false, false, null);
    }

    /**
@@ -260,9 +268,34 @@ public final class WindowInsets {
     * @return true if any inset values are nonzero
     */
    public boolean hasInsets() {
        return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets();
        return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
                || mDisplayCutout.hasCutout();
    }

    /**
     * @return the display cutout
     * @see DisplayCutout
     * @hide pending API
     */
    @NonNull
    public DisplayCutout getDisplayCutout() {
        return mDisplayCutout;
    }

    /**
     * Returns a copy of this WindowInsets with the cutout fully consumed.
     *
     * @return A modified copy of this WindowInsets
     * @hide pending API
     */
    public WindowInsets consumeCutout() {
        final WindowInsets result = new WindowInsets(this);
        result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
        result.mCutoutConsumed = true;
        return result;
    }


    /**
     * Check if these insets have been fully consumed.
     *
@@ -277,7 +310,8 @@ public final class WindowInsets {
     * @return true if the insets have been fully consumed.
     */
    public boolean isConsumed() {
        return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed;
        return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
                && mCutoutConsumed;
    }

    /**
@@ -495,7 +529,9 @@ public final class WindowInsets {
    public String toString() {
        return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
                + " windowDecorInsets=" + mWindowDecorInsets
                + " stableInsets=" + mStableInsets +
                (isRound() ? " round}" : "}");
                + " stableInsets=" + mStableInsets
                + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
                + (isRound() ? " round" : "")
                + "}";
    }
}
+347 −0

File added.

Preview size limit exceeded, changes collapsed.