Merge "Cutout: Add developer setting to mask the display cutout" into pi-dev
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 496bc9f..5f80d31 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -325,6 +325,7 @@
      *
      * @hide
      */
+    @VisibleForTesting
     public static DisplayCutout fromBoundingRect(int left, int top, int right, int bottom) {
         Region r = Region.obtain();
         r.set(left, top, right, bottom);
@@ -422,8 +423,11 @@
         m.postTranslate(offsetX, 0);
         p.transform(m);
 
-        addToRegion(p, r);
+        final Rect tmpRect = new Rect();
+        toRectAndAddToRegion(p, r, tmpRect);
+        final int topInset = tmpRect.bottom;
 
+        final int bottomInset;
         if (bottomSpec != null) {
             final Path bottomPath;
             try {
@@ -436,10 +440,17 @@
             m.postTranslate(0, displayHeight);
             bottomPath.transform(m);
             p.addPath(bottomPath);
-            addToRegion(bottomPath, r);
+            toRectAndAddToRegion(bottomPath, r, tmpRect);
+            bottomInset = displayHeight - tmpRect.top;
+        } else {
+            bottomInset = 0;
         }
 
-        final Pair<Path, DisplayCutout> result = new Pair<>(p, fromBounds(r));
+        // Reuse tmpRect as the inset rect we store into the DisplayCutout instance.
+        tmpRect.set(0, topInset, 0, bottomInset);
+        final DisplayCutout cutout = new DisplayCutout(tmpRect, r, false /* copyArguments */);
+
+        final Pair<Path, DisplayCutout> result = new Pair<>(p, cutout);
         synchronized (CACHE_LOCK) {
             sCachedSpec = spec;
             sCachedDisplayWidth = displayWidth;
@@ -450,12 +461,11 @@
         return result;
     }
 
-    private static void addToRegion(Path p, Region r) {
+    private static void toRectAndAddToRegion(Path p, Region inoutRegion, Rect inoutRect) {
         final RectF rectF = new RectF();
-        final Rect rect = new Rect();
         p.computeBounds(rectF, false /* unused */);
-        rectF.round(rect);
-        r.op(rect, Op.UNION);
+        rectF.round(inoutRect);
+        inoutRegion.op(inoutRect, Op.UNION);
     }
 
     private static Region boundingRectsToRegion(List<Rect> rects) {
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index fb7cd3d..9d6aee1 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2984,6 +2984,10 @@
          -->
     <bool name="config_fillMainBuiltInDisplayCutout">false</bool>
 
+    <!-- If true, and there is a cutout on the main built in display, the cutout will be masked
+         by shrinking the display such that it does not overlap the cutout area. -->
+    <bool name="config_maskMainBuiltInDisplayCutout">false</bool>
+
     <!-- 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/dimens.xml b/core/res/res/values/dimens.xml
index 471170b..73cb59e 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -61,6 +61,15 @@
     <!-- Margin at the edge of the screen to ignore touch events for in the windowshade. -->
     <dimen name="status_bar_edge_ignore">5dp</dimen>
 
+    <!-- Default radius of the software rounded corners. -->
+    <dimen name="rounded_corner_radius">0dp</dimen>
+    <!-- Radius of the software rounded corners at the top of the display in its natural
+        orientation. If zero, the value of rounded_corner_radius is used. -->
+    <dimen name="rounded_corner_radius_top">0dp</dimen>
+    <!-- Radius of the software rounded corners at the bottom of the display in its natural
+        orientation. If zero, the value of rounded_corner_radius is used. -->
+    <dimen name="rounded_corner_radius_bottom">0dp</dimen>
+
     <!-- Width of the window of the divider bar used to resize docked stacks. -->
     <dimen name="docked_stack_divider_thickness">48dp</dimen>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index cf624ac..cd9d0c4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3408,6 +3408,8 @@
   <java-symbol type="integer" name="config_defaultHapticFeedbackIntensity" />
   <java-symbol type="integer" name="config_defaultNotificationVibrationIntensity" />
 
+  <java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
+
   <java-symbol type="array" name="config_disableApksUnlessMatchedSku_apk_list" />
   <java-symbol type="array" name="config_disableApkUnlessMatchedSku_skus_list" />
 </resources>
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 6ee74cb..fe45fe7 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -19,6 +19,7 @@
 import static android.view.DisplayCutout.NO_CUTOUT;
 import static android.view.DisplayCutout.fromSpec;
 
+import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.not;
 import static org.hamcrest.Matchers.sameInstance;
 import static org.junit.Assert.assertEquals;
@@ -220,6 +221,19 @@
     }
 
     @Test
+    public void fromSpec_setsSafeInsets_top() {
+        DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z", 200, 400, 2f);
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 0)));
+    }
+
+    @Test
+    public void fromSpec_setsSafeInsets_top_and_bottom() {
+        DisplayCutout cutout = fromSpec("M -50,0 v 20 h 100 v -20 z"
+                + "@bottom M -50,0 v -10,0 h 100 v 20 z", 200, 400, 2f);
+        assertThat(cutout.getSafeInsets(), equalTo(new Rect(0, 20, 0, 10)));
+    }
+
+    @Test
     public void parcel_unparcel_nocutout() {
         Parcel p = Parcel.obtain();
 
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3c84e5a..7d90e02 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -939,9 +939,9 @@
     <dimen name="bottom_padding">48dp</dimen>
     <dimen name="edge_margin">8dp</dimen>
 
-    <dimen name="rounded_corner_radius">0dp</dimen>
-    <dimen name="rounded_corner_radius_top">0dp</dimen>
-    <dimen name="rounded_corner_radius_bottom">0dp</dimen>
+    <dimen name="rounded_corner_radius">@*android:dimen/rounded_corner_radius</dimen>
+    <dimen name="rounded_corner_radius_top">@*android:dimen/rounded_corner_radius_top</dimen>
+    <dimen name="rounded_corner_radius_bottom">@*android:dimen/rounded_corner_radius_bottom</dimen>
     <dimen name="rounded_corner_content_padding">0dp</dimen>
     <dimen name="nav_content_padding">0dp</dimen>
     <dimen name="nav_quick_scrub_track_edge_padding">24dp</dimen>
diff --git a/services/core/java/com/android/server/display/DisplayDeviceInfo.java b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
index 349e1c8..512e851 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceInfo.java
@@ -104,6 +104,12 @@
     public static final int FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 10;
 
     /**
+     * Flag: The display cutout of this display is masked.
+     * @hide
+     */
+    public static final int FLAG_MASK_DISPLAY_CUTOUT = 1 << 11;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
@@ -453,6 +459,9 @@
         if ((flags & FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
             msg.append(", FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD");
         }
+        if ((flags & FLAG_MASK_DISPLAY_CUTOUT) != 0) {
+            msg.append(", FLAG_MASK_DISPLAY_CUTOUT");
+        }
         return msg.toString();
     }
 }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 21ae048..16d82df 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -402,6 +402,10 @@
                             && SystemProperties.getBoolean(PROPERTY_EMULATOR_CIRCULAR, false))) {
                         mInfo.flags |= DisplayDeviceInfo.FLAG_ROUND;
                     }
+                    if (res.getBoolean(
+                            com.android.internal.R.bool.config_maskMainBuiltInDisplayCutout)) {
+                        mInfo.flags |= DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT;
+                    }
                     mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
                             mInfo.width, mInfo.height);
                     mInfo.type = Display.TYPE_BUILT_IN;
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 23ee56b..373de63 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -23,6 +23,8 @@
 import android.view.Surface;
 import android.view.SurfaceControl;
 
+import com.android.server.wm.utils.InsetUtils;
+
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.List;
@@ -251,14 +253,18 @@
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
             }
+            Rect maskingInsets = getMaskingInsets(deviceInfo);
+            int maskedWidth = deviceInfo.width - maskingInsets.left - maskingInsets.right;
+            int maskedHeight = deviceInfo.height - maskingInsets.top - maskingInsets.bottom;
+
             mBaseDisplayInfo.type = deviceInfo.type;
             mBaseDisplayInfo.address = deviceInfo.address;
             mBaseDisplayInfo.name = deviceInfo.name;
             mBaseDisplayInfo.uniqueId = deviceInfo.uniqueId;
-            mBaseDisplayInfo.appWidth = deviceInfo.width;
-            mBaseDisplayInfo.appHeight = deviceInfo.height;
-            mBaseDisplayInfo.logicalWidth = deviceInfo.width;
-            mBaseDisplayInfo.logicalHeight = deviceInfo.height;
+            mBaseDisplayInfo.appWidth = maskedWidth;
+            mBaseDisplayInfo.appHeight = maskedHeight;
+            mBaseDisplayInfo.logicalWidth = maskedWidth;
+            mBaseDisplayInfo.logicalHeight = maskedHeight;
             mBaseDisplayInfo.rotation = Surface.ROTATION_0;
             mBaseDisplayInfo.modeId = deviceInfo.modeId;
             mBaseDisplayInfo.defaultModeId = deviceInfo.defaultModeId;
@@ -275,13 +281,15 @@
             mBaseDisplayInfo.appVsyncOffsetNanos = deviceInfo.appVsyncOffsetNanos;
             mBaseDisplayInfo.presentationDeadlineNanos = deviceInfo.presentationDeadlineNanos;
             mBaseDisplayInfo.state = deviceInfo.state;
-            mBaseDisplayInfo.smallestNominalAppWidth = deviceInfo.width;
-            mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height;
-            mBaseDisplayInfo.largestNominalAppWidth = deviceInfo.width;
-            mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height;
+            mBaseDisplayInfo.smallestNominalAppWidth = maskedWidth;
+            mBaseDisplayInfo.smallestNominalAppHeight = maskedHeight;
+            mBaseDisplayInfo.largestNominalAppWidth = maskedWidth;
+            mBaseDisplayInfo.largestNominalAppHeight = maskedHeight;
             mBaseDisplayInfo.ownerUid = deviceInfo.ownerUid;
             mBaseDisplayInfo.ownerPackageName = deviceInfo.ownerPackageName;
-            mBaseDisplayInfo.displayCutout = deviceInfo.displayCutout;
+            boolean maskCutout =
+                    (deviceInfo.flags & DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT) != 0;
+            mBaseDisplayInfo.displayCutout = maskCutout ? null : deviceInfo.displayCutout;
 
             mPrimaryDisplayDeviceInfo = deviceInfo;
             mInfo = null;
@@ -289,6 +297,18 @@
     }
 
     /**
+     * Returns insets in ROTATION_0 for areas that are masked.
+     */
+    private static Rect getMaskingInsets(DisplayDeviceInfo deviceInfo) {
+        boolean maskCutout = (deviceInfo.flags & DisplayDeviceInfo.FLAG_MASK_DISPLAY_CUTOUT) != 0;
+        if (maskCutout && deviceInfo.displayCutout != null) {
+            return deviceInfo.displayCutout.getSafeInsets();
+        } else {
+            return new Rect();
+        }
+    }
+
+    /**
      * Applies the layer stack and transformation to the given display device
      * so that it shows the contents of this logical display.
      *
@@ -349,6 +369,12 @@
         int physWidth = rotated ? displayDeviceInfo.height : displayDeviceInfo.width;
         int physHeight = rotated ? displayDeviceInfo.width : displayDeviceInfo.height;
 
+        Rect maskingInsets = getMaskingInsets(displayDeviceInfo);
+        InsetUtils.rotateInsets(maskingInsets, orientation);
+        // Don't consider the masked area as available when calculating the scaling below.
+        physWidth -= maskingInsets.left + maskingInsets.right;
+        physHeight -= maskingInsets.top + maskingInsets.bottom;
+
         // Determine whether the width or height is more constrained to be scaled.
         //    physWidth / displayInfo.logicalWidth    => letter box
         // or physHeight / displayInfo.logicalHeight  => pillar box
@@ -375,6 +401,9 @@
         mTempDisplayRect.set(displayRectLeft, displayRectTop,
                 displayRectLeft + displayRectWidth, displayRectTop + displayRectHeight);
 
+        // Now add back the offset for the masked area.
+        mTempDisplayRect.offset(maskingInsets.left, maskingInsets.top);
+
         mTempDisplayRect.left += mDisplayOffsetX;
         mTempDisplayRect.right += mDisplayOffsetX;
         mTempDisplayRect.top += mDisplayOffsetY;
diff --git a/services/core/java/com/android/server/wm/utils/InsetUtils.java b/services/core/java/com/android/server/wm/utils/InsetUtils.java
index b4a998a..c8600dd 100644
--- a/services/core/java/com/android/server/wm/utils/InsetUtils.java
+++ b/services/core/java/com/android/server/wm/utils/InsetUtils.java
@@ -17,6 +17,7 @@
 package com.android.server.wm.utils;
 
 import android.graphics.Rect;
+import android.view.Surface;
 
 /**
  * Utility methods to handle insets represented as rects.
@@ -27,6 +28,32 @@
     }
 
     /**
+     * Transforms insets given in one rotation into insets in a different rotation.
+     *
+     * @param inOutInsets the insets to transform, is set to the transformed insets
+     * @param rotationDelta the delta between the new and old rotation.
+     *                      Must be one of Surface.ROTATION_0/90/180/270.
+     */
+    public static void rotateInsets(Rect inOutInsets, int rotationDelta) {
+        final Rect r = inOutInsets;
+        switch (rotationDelta) {
+            case Surface.ROTATION_0:
+                return;
+            case Surface.ROTATION_90:
+                r.set(r.top, r.right, r.bottom, r.left);
+                break;
+            case Surface.ROTATION_180:
+                r.set(r.right, r.bottom, r.left, r.top);
+                break;
+            case Surface.ROTATION_270:
+                r.set(r.bottom, r.left, r.top, r.right);
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown rotation: " + rotationDelta);
+        }
+    }
+
+    /**
      * Adds {@code insetsToAdd} to {@code inOutInsets}.
      */
     public static void addInsets(Rect inOutInsets, Rect insetsToAdd) {
diff --git a/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java b/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java
index d0f0fe3..08bcc3d 100644
--- a/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/utils/InsetUtilsTest.java
@@ -16,6 +16,11 @@
 
 package com.android.server.wm.utils;
 
+import static android.hardware.camera2.params.OutputConfiguration.ROTATION_90;
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+
 import static junit.framework.Assert.assertEquals;
 
 import android.graphics.Rect;
@@ -39,5 +44,29 @@
         InsetUtils.addInsets(rect1, rect2);
         assertEquals(new Rect(60, 80, 100, 120), rect1);
     }
+
+    @Test
+    public void rotate() {
+        final Rect original = new Rect(1, 2, 3, 4);
+
+        assertEquals("rot0", original, rotateCopy(original, ROTATION_0));
+
+        final Rect rot90 = rotateCopy(original, ROTATION_90);
+        assertEquals("rot90", new Rect(2, 3, 4, 1), rot90);
+
+        final Rect rot180 = rotateCopy(original, ROTATION_180);
+        assertEquals("rot180", new Rect(3, 4, 1, 2), rot180);
+        assertEquals("rot90(rot90)=rot180", rotateCopy(rot90, ROTATION_90), rot180);
+
+        final Rect rot270 = rotateCopy(original, ROTATION_270);
+        assertEquals("rot270", new Rect(4, 1, 2, 3), rot270);
+        assertEquals("rot90(rot180)=rot270", rotateCopy(rot180, ROTATION_90), rot270);
+    }
+
+    private static Rect rotateCopy(Rect insets, int rotationDelta) {
+        final Rect copy = new Rect(insets);
+        InsetUtils.rotateInsets(copy, rotationDelta);
+        return copy;
+    }
 }