Loading core/java/android/view/DisplayCutout.java +107 −0 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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. * Loading Loading @@ -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. * Loading core/tests/coretests/src/android/view/DisplayCutoutTest.java +79 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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 */); } } services/core/java/com/android/server/wm/DisplayContent.java +5 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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( Loading services/core/java/com/android/server/wm/utils/DisplayRotationUtil.javadeleted 100644 → 0 +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; } } services/core/java/com/android/server/wm/utils/WmDisplayCutout.java +1 −41 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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 Loading
core/java/android/view/DisplayCutout.java +107 −0 Original line number Diff line number Diff line Loading @@ -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; /** Loading Loading @@ -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. * Loading Loading @@ -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. * Loading
core/tests/coretests/src/android/view/DisplayCutoutTest.java +79 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading Loading @@ -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 */); } }
services/core/java/com/android/server/wm/DisplayContent.java +5 −16 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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( Loading
services/core/java/com/android/server/wm/utils/DisplayRotationUtil.javadeleted 100644 → 0 +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; } }
services/core/java/com/android/server/wm/utils/WmDisplayCutout.java +1 −41 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); } Loading @@ -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