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

Commit 4ab2c3c6 authored by Evan Rosky's avatar Evan Rosky Committed by Android (Google) Code Review
Browse files

Merge "Move rotate utility to DisplayCutout"

parents f0201257 3c12e168
Loading
Loading
Loading
Loading
+107 −0
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
@@ -369,6 +370,33 @@ public final class DisplayCutout {
                true);
    }

    /**
     * Creates a DisplayCutout instance.
     *
     * <p>Note that this is only useful for tests. For production code, developers should always
     * use a {@link DisplayCutout} obtained from the system.</p>
     *
     * @param safeInsets the insets from each edge which avoid the display cutout as returned by
     *                   {@link #getSafeInsetTop()} etc.
     * @param boundLeft the left bounding rect of the display cutout in pixels. If null is passed,
     *                  it's treated as an empty rectangle (0,0)-(0,0).
     * @param boundTop the top bounding rect of the display cutout in pixels.  If null is passed,
     *                  it's treated as an empty rectangle (0,0)-(0,0).
     * @param boundRight the right bounding rect of the display cutout in pixels.  If null is
     *                  passed, it's treated as an empty rectangle (0,0)-(0,0).
     * @param boundBottom the bottom bounding rect of the display cutout in pixels.  If null is
     *                   passed, it's treated as an empty rectangle (0,0)-(0,0).
     * @param waterfallInsets the insets for the curved areas in waterfall display.
     * @param info the cutout path parser info.
     * @hide
     */
    public DisplayCutout(@NonNull Insets safeInsets, @Nullable Rect boundLeft,
            @Nullable Rect boundTop, @Nullable Rect boundRight, @Nullable Rect boundBottom,
            @NonNull Insets waterfallInsets, @Nullable CutoutPathParserInfo info) {
        this(safeInsets.toRect(), waterfallInsets, boundLeft, boundTop, boundRight, boundBottom,
                info, true);
    }

    /**
     * Creates a DisplayCutout instance.
     *
@@ -1097,6 +1125,85 @@ public final class DisplayCutout {
                res.getDimensionPixelSize(R.dimen.waterfall_display_bottom_edge_size));
    }

    /**
     * @return a copy of this cutout that has been rotated for a display in toRotation.
     * @hide
     */
    public DisplayCutout getRotated(int startWidth, int startHeight,
            int fromRotation, int toRotation) {
        if (this == DisplayCutout.NO_CUTOUT) {
            return DisplayCutout.NO_CUTOUT;
        }
        final int rotation = RotationUtils.deltaRotation(fromRotation, toRotation);
        if (rotation == ROTATION_0) {
            return this;
        }
        final Insets waterfallInsets = RotationUtils.rotateInsets(getWaterfallInsets(), rotation);
        // returns a copy
        final Rect[] newBounds = getBoundingRectsAll();
        final Rect displayBounds = new Rect(0, 0, startWidth, startHeight);
        for (int i = 0; i < newBounds.length; ++i) {
            if (newBounds[i].isEmpty()) continue;
            RotationUtils.rotateBounds(newBounds[i], displayBounds, rotation);
        }
        Collections.rotate(Arrays.asList(newBounds), -rotation);
        final CutoutPathParserInfo info = getCutoutPathParserInfo();
        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
                info.getCutoutSpec(), toRotation, info.getScale());
        final boolean swapAspect = (rotation % 2) != 0;
        final int endWidth = swapAspect ? startHeight : startWidth;
        final int endHeight = swapAspect ? startWidth : startHeight;
        final DisplayCutout tmp =
                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo);
        final Rect safeInsets = DisplayCutout.computeSafeInsets(endWidth, endHeight, tmp);
        return tmp.replaceSafeInsets(safeInsets);
    }

    /**
     * Compute the insets derived from a cutout. This is usually used to populate the safe-insets
     * of the cutout via {@link #replaceSafeInsets}.
     * @hide
     */
    public static Rect computeSafeInsets(int displayW, int displayH, DisplayCutout cutout) {
        if (displayW == displayH) {
            throw new UnsupportedOperationException("not implemented: display=" + displayW + "x"
                    + displayH + " cutout=" + cutout);
        }

        int leftInset = Math.max(cutout.getWaterfallInsets().left, findCutoutInsetForSide(
                displayW, displayH, cutout.getBoundingRectLeft(), Gravity.LEFT));
        int topInset = Math.max(cutout.getWaterfallInsets().top, findCutoutInsetForSide(
                displayW, displayH, cutout.getBoundingRectTop(), Gravity.TOP));
        int rightInset = Math.max(cutout.getWaterfallInsets().right, findCutoutInsetForSide(
                displayW, displayH, cutout.getBoundingRectRight(), Gravity.RIGHT));
        int bottomInset = Math.max(cutout.getWaterfallInsets().bottom, findCutoutInsetForSide(
                displayW, displayH, cutout.getBoundingRectBottom(), Gravity.BOTTOM));

        return new Rect(leftInset, topInset, rightInset, bottomInset);
    }

    private static int findCutoutInsetForSide(int displayW, int displayH, Rect boundingRect,
            int gravity) {
        if (boundingRect.isEmpty()) {
            return 0;
        }

        int inset = 0;
        switch (gravity) {
            case Gravity.TOP:
                return Math.max(inset, boundingRect.bottom);
            case Gravity.BOTTOM:
                return Math.max(inset, displayH - boundingRect.top);
            case Gravity.LEFT:
                return Math.max(inset, boundingRect.right);
            case Gravity.RIGHT:
                return Math.max(inset, displayW - boundingRect.left);
            default:
                throw new IllegalArgumentException("unknown gravity: " + gravity);
        }
    }

    /**
     * Helper class for passing {@link DisplayCutout} through binder.
     *
+79 −0
Original line number Diff line number Diff line
@@ -19,6 +19,10 @@ package android.view;
import static android.view.DisplayCutout.NO_CUTOUT;
import static android.view.DisplayCutout.extractBoundsFromList;
import static android.view.DisplayCutout.fromSpec;
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 static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
@@ -497,6 +501,74 @@ public class DisplayCutoutTest {
                new ParcelableWrapper(mCutoutNumbers));
    }

    @Test
    public void testGetRotatedBounds_top_rot0() {
        int displayW = 500, displayH = 1000;
        DisplayCutout expected = new DisplayCutout(Insets.of(20, 100, 20, 0),
                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
                Insets.of(20, 0, 20, 0));
        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
                Insets.of(20, 0, 20, 0));
        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_0);
        assertEquals(expected, rotated);
    }

    @Test
    public void testGetRotatedBounds_top_rot90() {
        int displayW = 500, displayH = 1000;
        DisplayCutout expected = new DisplayCutout(Insets.of(100, 20, 0, 20),
                new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
                Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_90));
        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
                Insets.of(20, 0, 20, 0));
        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_90);
        assertEquals(expected, rotated);
    }

    @Test
    public void testGetRotatedBounds_top_rot180() {
        int displayW = 500, displayH = 1000;
        DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
                ZERO_RECT, ZERO_RECT, ZERO_RECT,
                new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
                Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
                Insets.of(20, 0, 20, 0));
        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_180);
        assertEquals(expected, rotated);
    }

    @Test
    public void testGetRotatedBounds_top_rot270() {
        int displayW = 500, displayH = 1000;
        DisplayCutout expected = new DisplayCutout(Insets.of(0, 20, 100, 20),
                ZERO_RECT, ZERO_RECT, new Rect(displayH - 100, 50, displayH - 0, 75), ZERO_RECT,
                Insets.of(0, 20, 0, 20), createParserInfo(ROTATION_270));
        DisplayCutout cutout = new DisplayCutout(Insets.of(20, 100, 20, 0),
                ZERO_RECT, new Rect(50, 0, 75, 100), ZERO_RECT, ZERO_RECT,
                Insets.of(20, 0, 20, 0));
        DisplayCutout rotated = cutout.getRotated(displayW, displayH, ROTATION_0, ROTATION_270);
        assertEquals(expected, rotated);
    }

    @Test
    public void testGetRotatedBounds_top_rot90to180() {
        int displayW = 500, displayH = 1000;
        DisplayCutout expected = new DisplayCutout(Insets.of(20, 0, 20, 100),
                ZERO_RECT, ZERO_RECT, ZERO_RECT,
                new Rect(displayW - 75, displayH - 100, displayW - 50, displayH - 0),
                Insets.of(20, 0, 20, 0), createParserInfo(ROTATION_180));
        DisplayCutout cutout = new DisplayCutout(Insets.of(100, 20, 0, 20),
                new Rect(0, displayW - 75, 100, displayW - 50), ZERO_RECT, ZERO_RECT, ZERO_RECT,
                Insets.of(0, 20, 0, 20));
        // starting from 90, so the start displayW/H are swapped:
        DisplayCutout rotated = cutout.getRotated(displayH, displayW, ROTATION_90, ROTATION_180);
        assertEquals(expected, rotated);
    }

    private static DisplayCutout createCutoutTop() {
        return createCutoutWithInsets(0, 100, 0, 0);
    }
@@ -533,4 +605,11 @@ public class DisplayCutoutTest {
                ZERO_RECT,
                waterfallInsets);
    }

    private static DisplayCutout.CutoutPathParserInfo createParserInfo(
            @Surface.Rotation int rotation) {
        return new DisplayCutout.CutoutPathParserInfo(
                0 /* displayWidth */, 0 /* displayHeight */, 0f /* density */, "" /* cutoutSpec */,
                rotation, 0f /* scale */);
    }
}
+5 −16
Original line number Diff line number Diff line
@@ -194,13 +194,13 @@ import android.util.ArraySet;
import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.RotationUtils;
import android.util.Size;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayCutout.CutoutPathParserInfo;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IDisplayWindowInsetsController;
@@ -239,7 +239,6 @@ import com.android.internal.util.function.pooled.PooledLambda;
import com.android.internal.util.function.pooled.PooledPredicate;
import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.wm.utils.DisplayRotationUtil;
import com.android.server.wm.utils.RotationCache;
import com.android.server.wm.utils.WmDisplayCutout;

@@ -584,8 +583,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
    /** Caches the value whether told display manager that we have content. */
    private boolean mLastHasContent;

    private static DisplayRotationUtil sRotationUtil = new DisplayRotationUtil();

    /**
     * The input method window for this display.
     */
@@ -2096,20 +2093,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
            return WmDisplayCutout.computeSafeInsets(
                    cutout, displayWidth, displayHeight);
        }
        final Insets waterfallInsets =
                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
        final DisplayCutout rotatedCutout =
                cutout.getRotated(displayWidth, displayHeight, ROTATION_0, rotation);
        final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
        final Rect[] newBounds = sRotationUtil.getRotatedBounds(
                cutout.getBoundingRectsAll(),
                rotation, displayWidth, displayHeight);
        final CutoutPathParserInfo info = cutout.getCutoutPathParserInfo();
        final CutoutPathParserInfo newInfo = new CutoutPathParserInfo(
                info.getDisplayWidth(), info.getDisplayHeight(), info.getDensity(),
                info.getCutoutSpec(), rotation, info.getScale());
        return WmDisplayCutout.computeSafeInsets(
                DisplayCutout.constructDisplayCutout(newBounds, waterfallInsets, newInfo),
        return new WmDisplayCutout(rotatedCutout, new Size(
                rotated ? displayHeight : displayWidth,
                rotated ? displayWidth : displayHeight);
                rotated ? displayWidth : displayHeight));
    }

    private WmDisplayCutout calculateDisplayCutoutForRotationUncached(
+0 −96
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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 com.android.server.wm.utils;

import static android.view.DisplayCutout.BOUNDS_POSITION_LENGTH;
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 static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates;

import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;

import com.android.internal.annotations.VisibleForTesting;

/**
 * Utility to compute bounds after rotating the screen.
 */
public class DisplayRotationUtil {
    private final Matrix mTmpMatrix = new Matrix();

    private static int getRotationToBoundsOffset(int rotation) {
        switch (rotation) {
            case ROTATION_0:
                return 0;
            case ROTATION_90:
                return -1;
            case ROTATION_180:
                return 2;
            case ROTATION_270:
                return 1;
            default:
                // should not happen
                return 0;
        }
    }

    @VisibleForTesting
    static int getBoundIndexFromRotation(int i, int rotation) {
        return Math.floorMod(i + getRotationToBoundsOffset(rotation),
                BOUNDS_POSITION_LENGTH);
    }

    /**
     * Compute bounds after rotating the screen.
     *
     * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements.
     * @param rotation rotation constant defined in android.view.Surface.
     * @param initialDisplayWidth width of the display before the rotation.
     * @param initialDisplayHeight height of the display before the rotation.
     * @return Bounds after the rotation.
     *
     * @hide
     */
    public Rect[] getRotatedBounds(
            Rect[] bounds, int rotation, int initialDisplayWidth, int initialDisplayHeight) {
        if (bounds.length != BOUNDS_POSITION_LENGTH) {
            throw new IllegalArgumentException(
                    "bounds must have exactly 4 elements: bounds=" + bounds);
        }
        if (rotation == ROTATION_0) {
            return bounds;
        }
        transformPhysicalToLogicalCoordinates(rotation, initialDisplayWidth, initialDisplayHeight,
                mTmpMatrix);
        Rect[] newBounds = new Rect[BOUNDS_POSITION_LENGTH];
        for (int i = 0; i < bounds.length; i++) {

            final Rect rect = bounds[i];
            if (!rect.isEmpty()) {
                final RectF rectF = new RectF(rect);
                mTmpMatrix.mapRect(rectF);
                rectF.round(rect);
            }
            newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
        }
        return newBounds;
    }
}
+1 −41
Original line number Diff line number Diff line
@@ -19,7 +19,6 @@ package com.android.server.wm.utils;
import android.graphics.Rect;
import android.util.Size;
import android.view.DisplayCutout;
import android.view.Gravity;

import java.util.Objects;

@@ -52,7 +51,7 @@ public class WmDisplayCutout {
            return NO_CUTOUT;
        }
        final Size displaySize = new Size(displayWidth, displayHeight);
        final Rect safeInsets = computeSafeInsets(displaySize, inner);
        final Rect safeInsets = DisplayCutout.computeSafeInsets(displayWidth, displayHeight, inner);
        return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
    }

@@ -66,45 +65,6 @@ public class WmDisplayCutout {
        return computeSafeInsets(mInner, width, height);
    }

    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
        if (displaySize.getWidth() == displaySize.getHeight()) {
            throw new UnsupportedOperationException("not implemented: display=" + displaySize +
                    " cutout=" + cutout);
        }

        int leftInset = Math.max(cutout.getWaterfallInsets().left,
                findCutoutInsetForSide(displaySize, cutout.getBoundingRectLeft(), Gravity.LEFT));
        int topInset = Math.max(cutout.getWaterfallInsets().top,
                findCutoutInsetForSide(displaySize, cutout.getBoundingRectTop(), Gravity.TOP));
        int rightInset = Math.max(cutout.getWaterfallInsets().right,
                findCutoutInsetForSide(displaySize, cutout.getBoundingRectRight(), Gravity.RIGHT));
        int bottomInset = Math.max(cutout.getWaterfallInsets().bottom,
                findCutoutInsetForSide(displaySize, cutout.getBoundingRectBottom(),
                        Gravity.BOTTOM));

        return new Rect(leftInset, topInset, rightInset, bottomInset);
    }

    private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
        if (boundingRect.isEmpty()) {
            return 0;
        }

        int inset = 0;
        switch (gravity) {
            case Gravity.TOP:
                return Math.max(inset, boundingRect.bottom);
            case Gravity.BOTTOM:
                return Math.max(inset, display.getHeight() - boundingRect.top);
            case Gravity.LEFT:
                return Math.max(inset, boundingRect.right);
            case Gravity.RIGHT:
                return Math.max(inset, display.getWidth() - boundingRect.left);
            default:
                throw new IllegalArgumentException("unknown gravity: " + gravity);
        }
    }

    public DisplayCutout getDisplayCutout() {
        return mInner;
    }
Loading