Make WmDisplayCutout support waterfall and long edge cutout

Bug: 146876976
Test: atest WmTests:WmDisplayCutoutTest
      atest DisplayLayoutTest
Change-Id: I1315533e17bd634f5db6be4276e66a00987dfc3d
diff --git a/core/java/android/util/RotationUtils.java b/core/java/android/util/RotationUtils.java
new file mode 100644
index 0000000..a44ed59
--- /dev/null
+++ b/core/java/android/util/RotationUtils.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 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 android.util;
+
+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.graphics.Insets;
+import android.view.Surface.Rotation;
+
+/**
+ * A class containing utility methods related to rotation.
+ *
+ * @hide
+ */
+public class RotationUtils {
+
+    /**
+     * Rotates an Insets according to the given rotation.
+     */
+    public static Insets rotateInsets(Insets insets, @Rotation int rotation) {
+        if (insets == null || insets == Insets.NONE) {
+            return insets;
+        }
+        Insets rotated;
+        switch (rotation) {
+            case ROTATION_0:
+                rotated = insets;
+                break;
+            case ROTATION_90:
+                rotated = Insets.of(
+                        insets.top,
+                        insets.right,
+                        insets.bottom,
+                        insets.left);
+                break;
+            case ROTATION_180:
+                rotated = Insets.of(
+                        insets.right,
+                        insets.bottom,
+                        insets.left,
+                        insets.top);
+                break;
+            case ROTATION_270:
+                rotated = Insets.of(
+                        insets.bottom,
+                        insets.left,
+                        insets.top,
+                        insets.right);
+                break;
+            default:
+                throw new IllegalArgumentException("unknown rotation: " + rotation);
+        }
+        return rotated;
+    }
+}
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index d437aa1..51a3c5d 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -33,9 +33,11 @@
     <dimen name="toast_y_offset">24dp</dimen>
     <!-- Height of the status bar -->
     <dimen name="status_bar_height">@dimen/status_bar_height_portrait</dimen>
-    <!-- Height of the status bar in portrait -->
+    <!-- Height of the status bar in portrait. The height should be
+         Max((status bar content height + waterfall top size), top cutout size) -->
     <dimen name="status_bar_height_portrait">24dp</dimen>
-    <!-- Height of the status bar in landscape -->
+    <!-- Height of the status bar in landscape. The height should be
+         Max((status bar content height + waterfall top size), top cutout size) -->
     <dimen name="status_bar_height_landscape">@dimen/status_bar_height_portrait</dimen>
     <!-- Height of area above QQS where battery/time go -->
     <dimen name="quick_qs_offset_height">48dp</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
index 264a683..64b0b66 100644
--- a/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayLayout.java
@@ -31,9 +31,11 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Insets;
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.provider.Settings;
+import android.util.RotationUtils;
 import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
@@ -43,8 +45,6 @@
 
 import com.android.internal.R;
 
-import java.util.List;
-
 /**
  * Contains information about the layout-properties of a display. This refers to internal layout
  * like insets/cutout/rotation. In general, this can be thought of as the System-UI analog to
@@ -323,28 +323,38 @@
         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
             return null;
         }
+        final Insets waterfallInsets =
+                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
         if (rotation == ROTATION_0) {
-            return computeSafeInsets(
-                    cutout, displayWidth, displayHeight);
+            return computeSafeInsets(cutout, displayWidth, displayHeight);
         }
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
-        Rect[] cutoutRects = computeSafeInsets(cutout, displayWidth, displayHeight)
-                        .getBoundingRectsAll();
+        Rect[] cutoutRects = cutout.getBoundingRectsAll();
         final Rect[] newBounds = new Rect[cutoutRects.length];
         final Rect displayBounds = new Rect(0, 0, displayWidth, displayHeight);
         for (int i = 0; i < cutoutRects.length; ++i) {
-            newBounds[i] = new Rect(cutoutRects[i]);
-            rotateBounds(newBounds[i], displayBounds, rotation);
+            final Rect rect = new Rect(cutoutRects[i]);
+            if (!rect.isEmpty()) {
+                rotateBounds(rect, displayBounds, rotation);
+            }
+            newBounds[getBoundIndexFromRotation(i, rotation)] = rect;
         }
-        return computeSafeInsets(DisplayCutout.fromBounds(newBounds),
+        return computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
                 rotated ? displayHeight : displayWidth,
                 rotated ? displayWidth : displayHeight);
     }
 
+    private static int getBoundIndexFromRotation(int index, int rotation) {
+        return (index - rotation) < 0
+                ? index - rotation + DisplayCutout.BOUNDS_POSITION_LENGTH
+                : index - rotation;
+    }
+
     /** Calculate safe insets. */
     public static DisplayCutout computeSafeInsets(DisplayCutout inner,
             int displayWidth, int displayHeight) {
-        if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+        if (inner == DisplayCutout.NO_CUTOUT) {
             return null;
         }
 
@@ -353,58 +363,44 @@
         return inner.replaceSafeInsets(safeInsets);
     }
 
-    private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
-        if (displaySize.getWidth() < displaySize.getHeight()) {
-            final List<Rect> boundingRects = cutout.replaceSafeInsets(
-                    new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
-                    .getBoundingRects();
-            int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
-            int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
-            return new Rect(0, topInset, 0, bottomInset);
-        } else if (displaySize.getWidth() > displaySize.getHeight()) {
-            final List<Rect> boundingRects = cutout.replaceSafeInsets(
-                    new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
-                    .getBoundingRects();
-            int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
-            int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
-            return new Rect(leftInset, 0, right, 0);
-        } else {
+    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 findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
-        int inset = 0;
-        final int size = boundingRects.size();
-        for (int i = 0; i < size; i++) {
-            Rect boundingRect = boundingRects.get(i);
-            switch (gravity) {
-                case Gravity.TOP:
-                    if (boundingRect.top == 0) {
-                        inset = Math.max(inset, boundingRect.bottom);
-                    }
-                    break;
-                case Gravity.BOTTOM:
-                    if (boundingRect.bottom == display.getHeight()) {
-                        inset = Math.max(inset, display.getHeight() - boundingRect.top);
-                    }
-                    break;
-                case Gravity.LEFT:
-                    if (boundingRect.left == 0) {
-                        inset = Math.max(inset, boundingRect.right);
-                    }
-                    break;
-                case Gravity.RIGHT:
-                    if (boundingRect.right == display.getWidth()) {
-                        inset = Math.max(inset, display.getWidth() - boundingRect.left);
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException("unknown gravity: " + gravity);
-            }
+    private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
+        if (boundingRect.isEmpty()) {
+            return 0;
         }
-        return inset;
+
+        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);
+        }
     }
 
     static boolean hasNavigationBar(DisplayInfo info, Context context, int displayId) {
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 825f93c..6979e3e 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -198,6 +198,7 @@
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.IntArray;
+import android.util.RotationUtils;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
@@ -1606,17 +1607,18 @@
         if (cutout == null || cutout == DisplayCutout.NO_CUTOUT) {
             return WmDisplayCutout.NO_CUTOUT;
         }
+        final Insets waterfallInsets =
+                RotationUtils.rotateInsets(cutout.getWaterfallInsets(), rotation);
         if (rotation == ROTATION_0) {
             return WmDisplayCutout.computeSafeInsets(
                     cutout, mInitialDisplayWidth, mInitialDisplayHeight);
         }
         final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
         final Rect[] newBounds = mRotationUtil.getRotatedBounds(
-                WmDisplayCutout.computeSafeInsets(
-                        cutout, mInitialDisplayWidth, mInitialDisplayHeight)
-                        .getDisplayCutout().getBoundingRectsAll(),
+                cutout.getBoundingRectsAll(),
                 rotation, mInitialDisplayWidth, mInitialDisplayHeight);
-        return WmDisplayCutout.computeSafeInsets(DisplayCutout.fromBounds(newBounds),
+        return WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(newBounds, waterfallInsets),
                 rotated ? mInitialDisplayHeight : mInitialDisplayWidth,
                 rotated ? mInitialDisplayWidth : mInitialDisplayHeight);
     }
diff --git a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
index 3be5d31..46fff03 100644
--- a/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
+++ b/services/core/java/com/android/server/wm/utils/WmDisplayCutout.java
@@ -21,7 +21,6 @@
 import android.view.DisplayCutout;
 import android.view.Gravity;
 
-import java.util.List;
 import java.util.Objects;
 
 /**
@@ -41,12 +40,17 @@
         mFrameSize = frameSize;
     }
 
-    public static WmDisplayCutout computeSafeInsets(DisplayCutout inner,
-            int displayWidth, int displayHeight) {
-        if (inner == DisplayCutout.NO_CUTOUT || inner.isBoundsEmpty()) {
+    /**
+     * Compute the safe insets according to the given DisplayCutout and the display size.
+     *
+     * @return return a WmDisplayCutout with calculated safe insets.
+     * @hide
+     */
+    public static WmDisplayCutout computeSafeInsets(
+            DisplayCutout inner, int displayWidth, int displayHeight) {
+        if (inner == DisplayCutout.NO_CUTOUT) {
             return NO_CUTOUT;
         }
-
         final Size displaySize = new Size(displayWidth, displayHeight);
         final Rect safeInsets = computeSafeInsets(displaySize, inner);
         return new WmDisplayCutout(inner.replaceSafeInsets(safeInsets), displaySize);
@@ -112,57 +116,42 @@
     }
 
     private static Rect computeSafeInsets(Size displaySize, DisplayCutout cutout) {
-        if (displaySize.getWidth() < displaySize.getHeight()) {
-            final List<Rect> boundingRects = cutout.replaceSafeInsets(
-                    new Rect(0, displaySize.getHeight() / 2, 0, displaySize.getHeight() / 2))
-                    .getBoundingRects();
-            int topInset = findInsetForSide(displaySize, boundingRects, Gravity.TOP);
-            int bottomInset = findInsetForSide(displaySize, boundingRects, Gravity.BOTTOM);
-            return new Rect(0, topInset, 0, bottomInset);
-        } else if (displaySize.getWidth() > displaySize.getHeight()) {
-            final List<Rect> boundingRects = cutout.replaceSafeInsets(
-                    new Rect(displaySize.getWidth() / 2, 0, displaySize.getWidth() / 2, 0))
-                    .getBoundingRects();
-            int leftInset = findInsetForSide(displaySize, boundingRects, Gravity.LEFT);
-            int right = findInsetForSide(displaySize, boundingRects, Gravity.RIGHT);
-            return new Rect(leftInset, 0, right, 0);
-        } else {
+        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 findInsetForSide(Size display, List<Rect> boundingRects, int gravity) {
-        int inset = 0;
-        final int size = boundingRects.size();
-        for (int i = 0; i < size; i++) {
-            Rect boundingRect = boundingRects.get(i);
-            switch (gravity) {
-                case Gravity.TOP:
-                    if (boundingRect.top == 0) {
-                        inset = Math.max(inset, boundingRect.bottom);
-                    }
-                    break;
-                case Gravity.BOTTOM:
-                    if (boundingRect.bottom == display.getHeight()) {
-                        inset = Math.max(inset, display.getHeight() - boundingRect.top);
-                    }
-                    break;
-                case Gravity.LEFT:
-                    if (boundingRect.left == 0) {
-                        inset = Math.max(inset, boundingRect.right);
-                    }
-                    break;
-                case Gravity.RIGHT:
-                    if (boundingRect.right == display.getWidth()) {
-                        inset = Math.max(inset, display.getWidth() - boundingRect.left);
-                    }
-                    break;
-                default:
-                    throw new IllegalArgumentException("unknown gravity: " + gravity);
-            }
+    private static int findCutoutInsetForSide(Size display, Rect boundingRect, int gravity) {
+        if (boundingRect.isEmpty()) {
+            return 0;
         }
-        return inset;
+
+        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() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
index fb8ba7b..a283476 100644
--- a/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/utils/WmDisplayCutoutTest.java
@@ -47,6 +47,7 @@
 @SmallTest
 @Presubmit
 public class WmDisplayCutoutTest {
+    private static final Rect ZERO_RECT = new Rect();
     private final DisplayCutout mCutoutTop = new DisplayCutout(
             Insets.of(0, 100, 0, 0),
             null /* boundLeft */, new Rect(50, 0, 75, 100) /* boundTop */,
@@ -99,41 +100,204 @@
     }
 
     @Test
-    public void computeSafeInsets_top() {
+    public void computeSafeInsets_cutoutTop() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                fromBoundingRect(0, 0, 100, 20, BOUNDS_POSITION_TOP), 200, 400);
+                fromBoundingRect(80, 0, 120, 20, BOUNDS_POSITION_TOP), 200, 400);
 
         assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
     }
 
     @Test
-    public void computeSafeInsets_left() {
+    public void computeSafeInsets_cutoutLeft() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                fromBoundingRect(0, 0, 20, 100, BOUNDS_POSITION_LEFT), 400, 200);
+                fromBoundingRect(0, 180, 20, 220, BOUNDS_POSITION_LEFT), 200, 400);
 
         assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
     }
 
     @Test
-    public void computeSafeInsets_bottom() {
+    public void computeSafeInsets_cutoutBottom() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                fromBoundingRect(0, 180, 100, 200, BOUNDS_POSITION_BOTTOM), 100, 200);
+                fromBoundingRect(80, 380, 120, 400, BOUNDS_POSITION_BOTTOM), 200, 400);
 
         assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
     }
 
     @Test
-    public void computeSafeInsets_right() {
+    public void computeSafeInsets_cutoutRight() {
         WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
-                fromBoundingRect(180, 0, 200, 100, BOUNDS_POSITION_RIGHT), 200, 100);
+                fromBoundingRect(180, 180, 200, 220, BOUNDS_POSITION_RIGHT), 200, 400);
 
         assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
     }
 
     @Test
+    public void computeSafeInsets_topLeftCornerCutout_portrait() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 20, 20, BOUNDS_POSITION_TOP), 200, 400);
+
+        assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_topRightCornerCutout_portrait() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(180, 0, 200, 20, BOUNDS_POSITION_TOP), 200, 400);
+
+        assertEquals(new Rect(0, 20, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_bottomLeftCornerCutout_portrait() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 380, 20, 400, BOUNDS_POSITION_BOTTOM), 200, 400);
+
+        assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_bottomRightCornerCutout_portrait() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(180, 380, 200, 400, BOUNDS_POSITION_BOTTOM), 200, 400);
+
+        assertEquals(new Rect(0, 0, 0, 20), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_topLeftCornerCutout_landscape() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 0, 20, 20, BOUNDS_POSITION_LEFT), 400, 200);
+
+        assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_topRightCornerCutout_landscape() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(380, 0, 400, 20, BOUNDS_POSITION_RIGHT), 400, 200);
+
+        assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_bottomLeftCornerCutout_landscape() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(0, 180, 20, 200, BOUNDS_POSITION_LEFT), 400, 200);
+
+        assertEquals(new Rect(20, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_bottomRightCornerCutout_landscape() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                fromBoundingRect(380, 180, 400, 200, BOUNDS_POSITION_RIGHT), 400, 200);
+
+        assertEquals(new Rect(0, 0, 20, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_waterfall() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, ZERO_RECT},
+                        Insets.of(1, 2, 3, 4)),
+                200, 400);
+
+        assertEquals(new Rect(1, 2, 3, 4), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutTop_greaterThan_waterfallTop() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
+                        Insets.of(0, 20, 0, 0)),
+                200, 400);
+
+        assertEquals(new Rect(0, 30, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutTop_lessThan_waterfallTop() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, new Rect(80, 0, 120, 30), ZERO_RECT, ZERO_RECT},
+                        Insets.of(0, 40, 0, 0)),
+                200, 400);
+
+        assertEquals(new Rect(0, 40, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutLeft_greaterThan_waterfallLeft() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
+                        Insets.of(20, 0, 0, 0)),
+                200, 400);
+
+        assertEquals(new Rect(30, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutLeft_lessThan_waterfallLeft() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {new Rect(0, 180, 30, 220), ZERO_RECT, ZERO_RECT, ZERO_RECT},
+                        Insets.of(40, 0, 0, 0)),
+                200, 400);
+
+        assertEquals(new Rect(40, 0, 0, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutBottom_greaterThan_waterfallBottom() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
+                        Insets.of(0, 0, 0, 20)),
+                200, 400);
+
+        assertEquals(new Rect(0, 0, 0, 30), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutBottom_lessThan_waterfallBottom() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, ZERO_RECT, ZERO_RECT, new Rect(80, 370, 120, 400)},
+                        Insets.of(0, 0, 0, 40)),
+                200, 400);
+
+        assertEquals(new Rect(0, 0, 0, 40), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutRight_greaterThan_waterfallRight() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
+                        Insets.of(0, 0, 20, 0)),
+                200, 400);
+
+        assertEquals(new Rect(0, 0, 30, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
+    public void computeSafeInsets_cutoutRight_lessThan_waterfallRight() {
+        WmDisplayCutout cutout = WmDisplayCutout.computeSafeInsets(
+                DisplayCutout.fromBoundsAndWaterfall(
+                        new Rect[] {ZERO_RECT, ZERO_RECT, new Rect(170, 180, 200, 220), ZERO_RECT},
+                        Insets.of(0, 0, 40, 0)),
+                200, 400);
+
+        assertEquals(new Rect(0, 0, 40, 0), cutout.getDisplayCutout().getSafeInsets());
+    }
+
+    @Test
     public void computeSafeInsets_bounds() {
-        DisplayCutout cutout = WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000,
-                2000).getDisplayCutout();
+        DisplayCutout cutout =
+                WmDisplayCutout.computeSafeInsets(mCutoutTop, 1000, 2000).getDisplayCutout();
 
         assertEquals(mCutoutTop.getBoundingRects(), cutout.getBoundingRects());
     }