16a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos/*
26a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * Copyright (C) 2018 The Android Open Source Project
36a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos *
46a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * Licensed under the Apache License, Version 2.0 (the "License");
56a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * you may not use this file except in compliance with the License.
66a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * You may obtain a copy of the License at
76a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos *
86a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos *      http://www.apache.org/licenses/LICENSE-2.0
96a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos *
106a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * Unless required by applicable law or agreed to in writing, software
116a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * distributed under the License is distributed on an "AS IS" BASIS,
126a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
136a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * See the License for the specific language governing permissions and
146a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * limitations under the License.
156a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos */
166a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
176a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roospackage com.android.server.wm.utils;
186a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
196a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roosimport android.graphics.Rect;
206a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roosimport android.util.Size;
216a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roosimport android.view.DisplayCutout;
226a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roosimport android.view.Gravity;
236a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
246a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roosimport java.util.List;
256a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roosimport java.util.Objects;
266a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
276a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos/**
286a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * Wrapper for DisplayCutout that also tracks the display size and using this allows (re)calculating
296a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos * safe insets.
306a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos */
316a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roospublic class WmDisplayCutout {
326a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
336a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public static final WmDisplayCutout NO_CUTOUT = new WmDisplayCutout(DisplayCutout.NO_CUTOUT,
346a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            null);
356a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
366a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    private final DisplayCutout mInner;
376a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    private final Size mFrameSize;
386a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
396a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public WmDisplayCutout(DisplayCutout inner, Size frameSize) {
406a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        mInner = inner;
416a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        mFrameSize = frameSize;
426a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
436a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
446a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public static WmDisplayCutout computeSafeInsets(DisplayCutout inner,
456a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            int displayWidth, int displayHeight) {
466a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
476a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            return NO_CUTOUT;
486a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        }
496a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
506a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        final Size displaySize = new Size(displayWidth, displayHeight);
516a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        final Rect safeInsets = computeSafeInsets(displaySize, inner);
526a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
536a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
546a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
556a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    /**
566a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * Insets the reference frame of the cutout in the given directions.
576a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     *
586a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * @return a copy of this instance which has been inset
596a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * @hide
606a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     */
616a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public WmDisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
626a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        DisplayCutout newInner = mInner.inset(insetLeft, insetTop, insetRight, insetBottom);
636a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
646a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        if (mInner == newInner) {
656a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            return this;
666a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        }
676a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
686a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        Size frame = mFrameSize == null ? null : new Size(
696a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                mFrameSize.getWidth() - insetLeft - insetRight,
706a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                mFrameSize.getHeight() - insetTop - insetBottom);
716a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
726a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return new WmDisplayCutout(newInner, frame);
736a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
746a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
756a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    /**
766a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * Recalculates the cutout relative to the given reference frame.
776a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     *
786a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * The safe insets must already have been computed, e.g. with {@link #computeSafeInsets}.
796a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     *
806a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * @return a copy of this instance with the safe insets recalculated
816a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * @hide
826a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     */
836a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public WmDisplayCutout calculateRelativeTo(Rect frame) {
846a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        if (mInner.isEmpty()) {
856a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            return this;
866a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        }
876a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return inset(frame.left, frame.top,
886a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                mFrameSize.getWidth() - frame.right, mFrameSize.getHeight() - frame.bottom);
896a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
906a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
916a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    /**
926a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * Calculates the safe insets relative to the given display size.
936a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     *
946a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * @return a copy of this instance with the safe insets calculated
956a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     * @hide
966a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos     */
976a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public WmDisplayCutout computeSafeInsets(int width, int height) {
986a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return computeSafeInsets(mInner, width, height);
996a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
1006a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
1016a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
1026a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        if (displaySize.getWidth() < displaySize.getHeight()) {
1036a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            final List<Rect> boundingRects = cutout.replaceSafeInsets(
1046a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
1056a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    .getBoundingRects();
1066a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
1076a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
1086a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            return new Rect(0, topInset, 0, bottomInset);
1096a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        } else if (displaySize.getWidth() > displaySize.getHeight()) {
1106a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            final List<Rect> boundingRects = cutout.replaceSafeInsets(
1116a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
1126a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    .getBoundingRects();
1136a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
1146a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
1156a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            return new Rect(leftInset, 0, right, 0);
1166a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        } else {
1176a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            throw new UnsupportedOperationException("not implemented: display=" + displaySize +
1186a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    " cutout=" + cutout);
1196a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        }
1206a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
1216a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
1226a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    private static int findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
1236a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        int inset = 0;
1246a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        final int size = boundingRects.size();
1256a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        for (int i = 0; i < size; i++) {
1266a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            Rect boundingRect = boundingRects.get(i);
1276a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            switch (gravity) {
1286a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                case Gravity.TOP:
1296a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    if (boundingRect.top == 0) {
1306a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                        inset = Math.max(inset, boundingRect.bottom);
1316a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    }
1326a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    break;
1336a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                case Gravity.BOTTOM:
1346a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    if (boundingRect.bottom == display.getHeight()) {
1356a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                        inset = Math.max(inset, display.getHeight() - boundingRect.top);
1366a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    }
1376a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    break;
1386a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                case Gravity.LEFT:
1396a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    if (boundingRect.left == 0) {
1406a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                        inset = Math.max(inset, boundingRect.right);
1416a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    }
1426a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    break;
1436a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                case Gravity.RIGHT:
1446a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    if (boundingRect.right == display.getWidth()) {
1456a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                        inset = Math.max(inset, display.getWidth() - boundingRect.left);
1466a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    }
1476a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    break;
1486a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                default:
1496a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                    throw new IllegalArgumentException("unknown gravity: " + gravity);
1506a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            }
1516a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        }
1526a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return inset;
1536a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
1546a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
1556a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public DisplayCutout getDisplayCutout() {
1566a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return mInner;
1576a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
1586a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
1596a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    @Override
1606a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public boolean equals(Object o) {
1616a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        if (!(o instanceof WmDisplayCutout)) {
1626a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos            return false;
1636a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        }
1646a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        WmDisplayCutout that = (WmDisplayCutout) o;
1656a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return Objects.equals(mInner, that.mInner) &&
1666a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos                Objects.equals(mFrameSize, that.mFrameSize);
1676a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
1686a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos
1696a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    @Override
1706a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    public int hashCode() {
1716a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos        return Objects.hash(mInner, mFrameSize);
1726a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos    }
1736a4fa0ec183e20c32e7816f5475e72fa9126356cAdrian Roos}
174