diff options
14 files changed, 348 insertions, 27 deletions
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java index 3dcfd00bc34c..1ef5f0950b16 100644 --- a/core/java/android/view/DisplayCutout.java +++ b/core/java/android/view/DisplayCutout.java @@ -153,7 +153,7 @@ public final class DisplayCutout { @Override public String toString() { return "DisplayCutout{insets=" + mSafeInsets - + " bounds=" + mBounds + + " boundingRect=" + getBoundingRect() + "}"; } @@ -279,9 +279,7 @@ public final class DisplayCutout { * @hide */ public static DisplayCutout fromBoundingPolygon(List<Point> points) { - Region bounds = Region.obtain(); Path path = new Path(); - path.reset(); for (int i = 0; i < points.size(); i++) { Point point = points.get(i); @@ -292,14 +290,24 @@ public final class DisplayCutout { } } path.close(); + return fromBounds(path); + } + /** + * Creates an instance from a bounding {@link Path}. + * + * @hide + */ + public static DisplayCutout fromBounds(Path path) { RectF clipRect = new RectF(); path.computeBounds(clipRect, false /* unused */); Region clipRegion = Region.obtain(); clipRegion.set((int) clipRect.left, (int) clipRect.top, (int) clipRect.right, (int) clipRect.bottom); + Region bounds = new Region(); bounds.setPath(path, clipRegion); + clipRegion.recycle(); return new DisplayCutout(ZERO_RECT, bounds); } @@ -329,12 +337,23 @@ public final class DisplayCutout { @Override public void writeToParcel(Parcel out, int flags) { - if (mInner == NO_CUTOUT) { + writeCutoutToParcel(mInner, out, flags); + } + + /** + * Writes a DisplayCutout to a {@link Parcel}. + * + * @see #readCutoutFromParcel(Parcel) + */ + public static void writeCutoutToParcel(DisplayCutout cutout, Parcel out, int flags) { + if (cutout == null) { + out.writeInt(-1); + } else if (cutout == NO_CUTOUT) { out.writeInt(0); } else { out.writeInt(1); - out.writeTypedObject(mInner.mSafeInsets, flags); - out.writeTypedObject(mInner.mBounds, flags); + out.writeTypedObject(cutout.mSafeInsets, flags); + out.writeTypedObject(cutout.mBounds, flags); } } @@ -345,13 +364,13 @@ public final class DisplayCutout { * Needed for AIDL out parameters. */ public void readFromParcel(Parcel in) { - mInner = readCutout(in); + mInner = readCutoutFromParcel(in); } public static final Creator<ParcelableWrapper> CREATOR = new Creator<ParcelableWrapper>() { @Override public ParcelableWrapper createFromParcel(Parcel in) { - return new ParcelableWrapper(readCutout(in)); + return new ParcelableWrapper(readCutoutFromParcel(in)); } @Override @@ -360,8 +379,17 @@ public final class DisplayCutout { } }; - private static DisplayCutout readCutout(Parcel in) { - if (in.readInt() == 0) { + /** + * Reads a DisplayCutout from a {@link Parcel}. + * + * @see #writeCutoutToParcel(DisplayCutout, Parcel, int) + */ + public static DisplayCutout readCutoutFromParcel(Parcel in) { + int variant = in.readInt(); + if (variant == -1) { + return null; + } + if (variant == 0) { return NO_CUTOUT; } diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java index b813ddb63859..37e9815c93c5 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -149,6 +149,13 @@ public final class DisplayInfo implements Parcelable { public int overscanBottom; /** + * The {@link DisplayCutout} if present, otherwise {@code null}. + * + * @hide + */ + public DisplayCutout displayCutout; + + /** * The rotation of the display relative to its natural orientation. * May be one of {@link android.view.Surface#ROTATION_0}, * {@link android.view.Surface#ROTATION_90}, {@link android.view.Surface#ROTATION_180}, @@ -301,6 +308,7 @@ public final class DisplayInfo implements Parcelable { && overscanTop == other.overscanTop && overscanRight == other.overscanRight && overscanBottom == other.overscanBottom + && Objects.equal(displayCutout, other.displayCutout) && rotation == other.rotation && modeId == other.modeId && defaultModeId == other.defaultModeId @@ -342,6 +350,7 @@ public final class DisplayInfo implements Parcelable { overscanTop = other.overscanTop; overscanRight = other.overscanRight; overscanBottom = other.overscanBottom; + displayCutout = other.displayCutout; rotation = other.rotation; modeId = other.modeId; defaultModeId = other.defaultModeId; @@ -379,6 +388,7 @@ public final class DisplayInfo implements Parcelable { overscanTop = source.readInt(); overscanRight = source.readInt(); overscanBottom = source.readInt(); + displayCutout = DisplayCutout.ParcelableWrapper.readCutoutFromParcel(source); rotation = source.readInt(); modeId = source.readInt(); defaultModeId = source.readInt(); @@ -425,6 +435,7 @@ public final class DisplayInfo implements Parcelable { dest.writeInt(overscanTop); dest.writeInt(overscanRight); dest.writeInt(overscanBottom); + DisplayCutout.ParcelableWrapper.writeCutoutToParcel(displayCutout, dest, flags); dest.writeInt(rotation); dest.writeInt(modeId); dest.writeInt(defaultModeId); diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index d2685cfb5a8f..2440e9b43116 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -2769,6 +2769,13 @@ some existing device-specific resource overlays. --> <bool name="config_mainBuiltInDisplayIsRound">@bool/config_windowIsRound</bool> + <!-- The bounding path of the cutout region of the main built-in display. + Must either be empty if there is no cutout region, or a string that is parsable by + {@link android.util.PathParser}. + The path is assumed to be specified in display coordinates with pixel units and in + the display's native orientation. --> + <string translatable="false" name="config_mainBuiltInDisplayCutout"></string> + <!-- Ultrasound support for Mic/speaker path --> <!-- Whether the default microphone audio source supports near-ultrasound frequencies (range of 18 - 21 kHz). --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index d2ad99b4b744..ac3d4020a3b0 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3200,6 +3200,7 @@ <java-symbol type="string" name="battery_saver_warning_title" /> <java-symbol type="string" name="global_action_logout" /> + <java-symbol type="string" name="config_mainBuiltInDisplayCutout" /> <java-symbol type="drawable" name="ic_logout" /> <java-symbol type="array" name="config_autoBrightnessDisplayValuesNits" /> diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java index 6aa465ce9f8c..a1022604c27f 100644 --- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java +++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java @@ -24,6 +24,7 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.PixelFormat; import android.graphics.Point; +import android.graphics.PorterDuff; import android.graphics.Region; import android.os.Handler; import android.os.Looper; @@ -114,10 +115,9 @@ public class EmulatedDisplayCutout extends SystemUI { @Override public WindowInsets onApplyWindowInsets(WindowInsets insets) { + mBounds.reset(); if (insets.getDisplayCutout() != null) { insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds); - } else { - mBounds.reset(); } invalidate(); return insets.consumeDisplayCutout(); @@ -126,7 +126,7 @@ public class EmulatedDisplayCutout extends SystemUI { @Override protected void onDraw(Canvas canvas) { if (!mBounds.isEmpty()) { - mPaint.setColor(Color.DKGRAY); + mPaint.setColor(Color.BLACK); mPaint.setStyle(Paint.Style.FILL); canvas.drawPath(mBounds, mPaint); diff --git a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java index b15b79fb984e..6f7a270799bc 100644 --- a/packages/SystemUI/src/com/android/systemui/RoundedCorners.java +++ b/packages/SystemUI/src/com/android/systemui/RoundedCorners.java @@ -163,6 +163,7 @@ public class RoundedCorners extends SystemUI implements Tunable { | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY; lp.setTitle("RoundedOverlay"); lp.gravity = Gravity.TOP; + lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA; return lp; } diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java index fddb81ba2af7..6db3b44c17b2 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java @@ -19,6 +19,7 @@ package com.android.server.display; import android.hardware.display.DisplayViewport; import android.util.DisplayMetrics; import android.view.Display; +import android.view.DisplayCutout; import android.view.Surface; import java.util.Arrays; @@ -229,6 +230,11 @@ final class DisplayDeviceInfo { public int flags; /** + * The {@link DisplayCutout} if present or {@code null} otherwise. + */ + public DisplayCutout displayCutout; + + /** * The touch attachment, per {@link DisplayViewport#touch}. */ public int touch; @@ -321,6 +327,7 @@ final class DisplayDeviceInfo { || appVsyncOffsetNanos != other.appVsyncOffsetNanos || presentationDeadlineNanos != other.presentationDeadlineNanos || flags != other.flags + || !Objects.equal(displayCutout, other.displayCutout) || touch != other.touch || rotation != other.rotation || type != other.type @@ -354,6 +361,7 @@ final class DisplayDeviceInfo { appVsyncOffsetNanos = other.appVsyncOffsetNanos; presentationDeadlineNanos = other.presentationDeadlineNanos; flags = other.flags; + displayCutout = other.displayCutout; touch = other.touch; rotation = other.rotation; type = other.type; @@ -380,6 +388,9 @@ final class DisplayDeviceInfo { sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi"); sb.append(", appVsyncOff ").append(appVsyncOffsetNanos); sb.append(", presDeadline ").append(presentationDeadlineNanos); + if (displayCutout != null) { + sb.append(", cutout ").append(displayCutout); + } sb.append(", touch ").append(touchToString(touch)); sb.append(", rotation ").append(rotation); sb.append(", type ").append(Display.typeToString(type)); diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java index eb9ff589d5f6..483b02c2bf65 100644 --- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java @@ -30,9 +30,12 @@ import android.os.Looper; import android.os.PowerManager; import android.os.SystemProperties; import android.os.Trace; +import android.text.TextUtils; +import android.util.PathParser; import android.util.Slog; import android.util.SparseArray; import android.view.Display; +import android.view.DisplayCutout; import android.view.DisplayEventReceiver; import android.view.Surface; import android.view.SurfaceControl; @@ -400,12 +403,14 @@ final class LocalDisplayAdapter extends DisplayAdapter { && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) { mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND; } + mInfo.displayCutout = parseDefaultDisplayCutout(res); mInfo.type = Display.TYPE_BUILT_IN; mInfo.densityDpi = (int)(phys.density * 160 + 0.5f); mInfo.xDpi = phys.xDpi; mInfo.yDpi = phys.yDpi; mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; } else { + mInfo.displayCutout = null; mInfo.type = Display.TYPE_HDMI; mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION; mInfo.name = getContext().getResources().getString( @@ -434,6 +439,15 @@ final class LocalDisplayAdapter extends DisplayAdapter { return mInfo; } + private DisplayCutout parseDefaultDisplayCutout(Resources res) { + String cutoutString = res.getString( + com.android.internal.R.string.config_mainBuiltInDisplayCutout); + if (TextUtils.isEmpty(cutoutString)) { + return null; + } + return DisplayCutout.fromBounds(PathParser.createPathFromPathData(cutoutString)); + } + @Override public Runnable requestDisplayStateLocked(final int state, final int brightness) { // Assume that the brightness is off if the display is being turned off. diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java index 78a540790db6..132f083c1adc 100644 --- a/services/core/java/com/android/server/display/LogicalDisplay.java +++ b/services/core/java/com/android/server/display/LogicalDisplay.java @@ -145,6 +145,7 @@ final class LogicalDisplay { mInfo.overscanRight = mOverrideDisplayInfo.overscanRight; mInfo.overscanBottom = mOverrideDisplayInfo.overscanBottom; mInfo.rotation = mOverrideDisplayInfo.rotation; + mInfo.displayCutout = mOverrideDisplayInfo.displayCutout; mInfo.logicalDensityDpi = mOverrideDisplayInfo.logicalDensityDpi; mInfo.physicalXDpi = mOverrideDisplayInfo.physicalXDpi; mInfo.physicalYDpi = mOverrideDisplayInfo.physicalYDpi; @@ -280,6 +281,7 @@ final class LogicalDisplay { mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height; mBaseDisplayInfo.ownerUid = deviceInfo.ownerUid; mBaseDisplayInfo.ownerPackageName = deviceInfo.ownerPackageName; + mBaseDisplayInfo.displayCutout = deviceInfo.displayCutout; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo = null; diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 252eff567cfd..df468acce74c 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -62,6 +62,7 @@ import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_A import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; +import static com.android.server.wm.utils.CoordinateTransforms.transformPhysicalToLogicalCoordinates; import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT; @@ -121,6 +122,7 @@ import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Matrix; +import android.graphics.Path; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Region; @@ -137,6 +139,7 @@ import android.util.MutableBoolean; import android.util.Slog; import android.util.proto.ProtoOutputStream; import android.view.Display; +import android.view.DisplayCutout; import android.view.DisplayInfo; import android.view.InputDevice; import android.view.MagnificationSpec; @@ -158,6 +161,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Predicate; @@ -208,6 +212,9 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo int mInitialDisplayHeight = 0; int mInitialDisplayDensity = 0; + DisplayCutout mInitialDisplayCutout; + DisplayCutout mDisplayCutoutOverride; + /** * Overridden display size. Initialized with {@link #mInitialDisplayWidth} * and {@link #mInitialDisplayHeight}, but can be set via shell command "adb shell wm size". @@ -1193,6 +1200,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mDisplayInfo.getLogicalMetrics(mRealDisplayMetrics, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); } + mDisplayInfo.displayCutout = calculateDisplayCutoutForCurrentRotation(); mDisplayInfo.getAppMetrics(mDisplayMetrics); if (mDisplayScalingDisabled) { mDisplayInfo.flags |= Display.FLAG_SCALING_DISABLED; @@ -1214,6 +1222,18 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo return mDisplayInfo; } + DisplayCutout calculateDisplayCutoutForCurrentRotation() { + final DisplayCutout cutout = mInitialDisplayCutout; + if (cutout == null || cutout == DisplayCutout.NO_CUTOUT || mRotation == ROTATION_0) { + return cutout; + } + final Path bounds = cutout.getBounds().getBoundaryPath(); + transformPhysicalToLogicalCoordinates(mRotation, mInitialDisplayWidth, + mInitialDisplayHeight, mTmpMatrix); + bounds.transform(mTmpMatrix); + return DisplayCutout.fromBounds(bounds); + } + /** * Compute display configuration based on display properties and policy settings. * Do not call if mDisplayReady == false. @@ -1676,6 +1696,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInitialDisplayWidth = mDisplayInfo.logicalWidth; mInitialDisplayHeight = mDisplayInfo.logicalHeight; mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi; + mInitialDisplayCutout = mDisplayInfo.displayCutout; } /** @@ -1690,10 +1711,12 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth; final int newHeight = rotated ? mDisplayInfo.logicalWidth : mDisplayInfo.logicalHeight; final int newDensity = mDisplayInfo.logicalDensityDpi; + final DisplayCutout newCutout = mDisplayInfo.displayCutout; final boolean displayMetricsChanged = mInitialDisplayWidth != newWidth || mInitialDisplayHeight != newHeight - || mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi; + || mInitialDisplayDensity != mDisplayInfo.logicalDensityDpi + || !Objects.equals(mInitialDisplayCutout, newCutout); if (displayMetricsChanged) { // Check if display size or density is forced. @@ -1710,6 +1733,7 @@ class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowCo mInitialDisplayWidth = newWidth; mInitialDisplayHeight = newHeight; mInitialDisplayDensity = newDensity; + mInitialDisplayCutout = newCutout; mService.reconfigureDisplayLocked(this); } } diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java index 015571255f0d..bd06192b09fd 100644 --- a/services/core/java/com/android/server/wm/DisplayFrames.java +++ b/services/core/java/com/android/server/wm/DisplayFrames.java @@ -22,6 +22,7 @@ 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; @@ -100,9 +101,12 @@ public class DisplayFrames { /** During layout, the current screen borders along which input method windows are placed. */ public final Rect mDock = new Rect(); - /** Definition of the cutout */ + /** The display cutout used for layout (after rotation and emulation) */ @NonNull public DisplayCutout mDisplayCutout = DisplayCutout.NO_CUTOUT; + /** The cutout as supplied by display info */ + @NonNull private DisplayCutout mDisplayInfoCutout = DisplayCutout.NO_CUTOUT; + /** * During layout, the frame that is display-cutout safe, i.e. that does not intersect with it. */ @@ -126,6 +130,8 @@ public class DisplayFrames { mRotation = info.rotation; mDisplayInfoOverscan.set( info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom); + mDisplayInfoCutout = info.displayCutout != null + ? info.displayCutout : DisplayCutout.NO_CUTOUT; } public void onBeginLayout(boolean emulateDisplayCutout, int statusBarHeight) { @@ -166,12 +172,29 @@ public class DisplayFrames { mStable.set(mUnrestricted); mStableFullscreen.set(mUnrestricted); mCurrent.set(mUnrestricted); - mDisplayCutout = DisplayCutout.NO_CUTOUT; - mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, - Integer.MAX_VALUE, Integer.MAX_VALUE); + mDisplayCutout = mDisplayInfoCutout; if (emulateDisplayCutout) { setEmulatedDisplayCutout((int) (statusBarHeight * 0.8)); } + mDisplayCutout = mDisplayCutout.calculateRelativeTo(mOverscan); + + mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, + Integer.MAX_VALUE, Integer.MAX_VALUE); + if (!mDisplayCutout.isEmpty()) { + final DisplayCutout c = mDisplayCutout; + if (c.getSafeInsetLeft() > 0) { + mDisplayCutoutSafe.left = mRestrictedOverscan.left + c.getSafeInsetLeft(); + } + if (c.getSafeInsetTop() > 0) { + mDisplayCutoutSafe.top = mRestrictedOverscan.top + c.getSafeInsetTop(); + } + if (c.getSafeInsetRight() > 0) { + mDisplayCutoutSafe.right = mRestrictedOverscan.right - c.getSafeInsetRight(); + } + if (c.getSafeInsetBottom() > 0) { + mDisplayCutoutSafe.bottom = mRestrictedOverscan.bottom - c.getSafeInsetBottom(); + } + } } public int getInputMethodWindowVisibleHeight() { @@ -194,8 +217,7 @@ public class DisplayFrames { new Point(height, (screenWidth - widthBottom) / 2), new Point(height, (screenWidth + widthBottom) / 2), new Point(0, (screenWidth + widthTop) / 2) - )).calculateRelativeTo(mUnrestricted); - mDisplayCutoutSafe.left = height; + )); break; case ROTATION_180: mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( @@ -203,8 +225,7 @@ public class DisplayFrames { new Point((screenWidth - widthBottom) / 2, screenHeight - height), new Point((screenWidth + widthBottom) / 2, screenHeight - height), new Point((screenWidth + widthTop) / 2, screenHeight) - )).calculateRelativeTo(mUnrestricted); - mDisplayCutoutSafe.bottom = screenHeight - height; + )); break; case ROTATION_270: mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( @@ -212,8 +233,7 @@ public class DisplayFrames { new Point(screenHeight - height, (screenWidth - widthBottom) / 2), new Point(screenHeight - height, (screenWidth + widthBottom) / 2), new Point(screenHeight, (screenWidth + widthTop) / 2) - )).calculateRelativeTo(mUnrestricted); - mDisplayCutoutSafe.right = screenHeight - height; + )); break; default: mDisplayCutout = DisplayCutout.fromBoundingPolygon(Arrays.asList( @@ -221,8 +241,7 @@ public class DisplayFrames { new Point((screenWidth - widthBottom) / 2, height), new Point((screenWidth + widthBottom) / 2, height), new Point((screenWidth + widthTop) / 2, 0) - )).calculateRelativeTo(mUnrestricted); - mDisplayCutoutSafe.top = height; + )); break; } } diff --git a/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java new file mode 100644 index 000000000000..09d7b5de1caf --- /dev/null +++ b/services/core/java/com/android/server/wm/utils/CoordinateTransforms.java @@ -0,0 +1,62 @@ +/* + * 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.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.Dimension; +import android.graphics.Matrix; +import android.view.Surface.Rotation; + +public class CoordinateTransforms { + + private CoordinateTransforms() { + } + + /** + * Sets a matrix such that given a rotation, it transforms physical display + * coordinates to that rotation's logical coordinates. + * + * @param rotation the rotation to which the matrix should transform + * @param out the matrix to be set + */ + public static void transformPhysicalToLogicalCoordinates(@Rotation int rotation, + @Dimension int physicalWidth, @Dimension int physicalHeight, Matrix out) { + switch (rotation) { + case ROTATION_0: + out.reset(); + break; + case ROTATION_90: + out.setRotate(270); + out.postTranslate(0, physicalWidth); + break; + case ROTATION_180: + out.setRotate(180); + out.postTranslate(physicalWidth, physicalHeight); + break; + case ROTATION_270: + out.setRotate(90); + out.postTranslate(physicalHeight, 0); + break; + default: + throw new IllegalArgumentException("Unknown rotation: " + rotation); + } + } +} diff --git a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java index 693264cbe4ee..2284bbbbf3ac 100644 --- a/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/servicestests/src/com/android/server/wm/DisplayContentTests.java @@ -37,13 +37,18 @@ import org.junit.runner.RunWith; import android.annotation.SuppressLint; import android.content.res.Configuration; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; import android.os.SystemClock; import android.platform.test.annotations.Presubmit; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.util.DisplayMetrics; import android.util.SparseIntArray; +import android.view.DisplayCutout; import android.view.MotionEvent; +import android.view.Surface; import java.util.Arrays; import java.util.LinkedList; @@ -53,7 +58,7 @@ import java.util.List; * Tests for the {@link DisplayContent} class. * * Build/Install/Run: - * bit FrameworksServicesTests:com.android.server.wm.DisplayContentTests + * atest com.android.server.wm.DisplayContentTests */ @SmallTest @Presubmit @@ -385,6 +390,38 @@ public class DisplayContentTests extends WindowTestsBase { } @Test + public void testDisplayCutout_rot0() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + final DisplayContent dc = createNewDisplay(); + dc.mInitialDisplayWidth = 200; + dc.mInitialDisplayHeight = 400; + final DisplayCutout cutout = createCutout(new Rect(80, 0, 120, 10)); + + dc.mInitialDisplayCutout = cutout; + dc.setRotation(Surface.ROTATION_0); + dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. + + assertEquals(cutout, dc.getDisplayInfo().displayCutout); + } + } + + @Test + public void testDisplayCutout_rot90() throws Exception { + synchronized (sWm.getWindowManagerLock()) { + final DisplayContent dc = createNewDisplay(); + dc.mInitialDisplayWidth = 200; + dc.mInitialDisplayHeight = 400; + final DisplayCutout cutout = createCutout(new Rect(80, 0, 120, 10)); + + dc.mInitialDisplayCutout = cutout; + dc.setRotation(Surface.ROTATION_90); + dc.computeScreenConfiguration(new Configuration()); // recomputes dc.mDisplayInfo. + + assertEquals(createCutout(new Rect(0, 80, 10, 120)), dc.getDisplayInfo().displayCutout); + } + } + + @Test @SuppressLint("InlinedApi") public void testOrientationDefinedByKeyguard() { final DisplayContent dc = createNewDisplay(); @@ -449,4 +486,10 @@ public class DisplayContentTests extends WindowTestsBase { y, metaState); } + + private DisplayCutout createCutout(Rect r) { + Path p = new Path(); + p.addRect(r.left, r.top, r.right, r.bottom, Path.Direction.CCW); + return DisplayCutout.fromBounds(p); + } } diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java new file mode 100644 index 000000000000..40a10e04c893 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/wm/utils/CoordinateTransformsTest.java @@ -0,0 +1,98 @@ +/* + * 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.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 static org.hamcrest.Matchers.is; +import static org.junit.Assert.*; + +import android.graphics.Matrix; +import android.graphics.Point; +import android.graphics.PointF; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ErrorCollector; + +public class CoordinateTransformsTest { + + private static final int W = 200; + private static final int H = 400; + + private final Matrix mMatrix = new Matrix(); + + @Rule + public final ErrorCollector mErrorCollector = new ErrorCollector(); + + @Before + public void setUp() throws Exception { + mMatrix.setTranslate(0xdeadbeef, 0xdeadbeef); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot0() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_0, W, H, mMatrix); + assertThat(mMatrix, is(Matrix.IDENTITY_MATRIX)); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot90() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_90, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(0, W); + checkDevicePoint(W, H).mapsToLogicalPoint(H, 0); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot180() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_180, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(W, H); + checkDevicePoint(W, H).mapsToLogicalPoint(0, 0); + } + + @Test + public void transformPhysicalToLogicalCoordinates_rot270() throws Exception { + transformPhysicalToLogicalCoordinates(ROTATION_270, W, H, mMatrix); + + checkDevicePoint(0, 0).mapsToLogicalPoint(H, 0); + checkDevicePoint(W, H).mapsToLogicalPoint(0, W); + } + + private DevicePointAssertable checkDevicePoint(int x, int y) { + final Point devicePoint = new Point(x, y); + final float[] fs = new float[] {x, y}; + mMatrix.mapPoints(fs); + final PointF transformedPoint = new PointF(fs[0], fs[1]); + + return (expectedX, expectedY) -> { + mErrorCollector.checkThat("t(" + devicePoint + ")", + transformedPoint, is(new PointF(expectedX, expectedY))); + }; + } + + public interface DevicePointAssertable { + void mapsToLogicalPoint(int x, int y); + } +}
\ No newline at end of file |