Only update mAboveInsetsState when necessary

This CL only updates mAboveInsetsState while the z-order of a window is
changed or the display info has been updated.

This can be a step to make client compute its window frame locally.

Bug: 161810301
Bug: 175861127
Test: atest DisplayPolicyTests DisplayPolicyLayoutTests
Change-Id: Ic202ca82ae3b0aeaf4d95ba6d0847970aa9896a6
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index 219190f..21dd1fb 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -20,8 +20,6 @@
 import static android.view.InsetsStateProto.DISPLAY_FRAME;
 import static android.view.InsetsStateProto.SOURCES;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
-import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
-import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
 import static android.view.WindowInsets.Type.displayCutout;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.indexOf;
@@ -106,14 +104,11 @@
     public static final int ITYPE_NAVIGATION_BAR = 1;
     public static final int ITYPE_CAPTION_BAR = 2;
 
-    // The always visible types are visible to all windows regardless of the z-order.
-    public static final int FIRST_ALWAYS_VISIBLE_TYPE = 3;
-    public static final int ITYPE_TOP_GESTURES = FIRST_ALWAYS_VISIBLE_TYPE;
+    public static final int ITYPE_TOP_GESTURES = 3;
     public static final int ITYPE_BOTTOM_GESTURES = 4;
     public static final int ITYPE_LEFT_GESTURES = 5;
     public static final int ITYPE_RIGHT_GESTURES = 6;
 
-    /** Additional gesture inset types that map into {@link Type.MANDATORY_SYSTEM_GESTURES}. */
     public static final int ITYPE_TOP_MANDATORY_GESTURES = 7;
     public static final int ITYPE_BOTTOM_MANDATORY_GESTURES = 8;
     public static final int ITYPE_LEFT_MANDATORY_GESTURES = 9;
@@ -123,7 +118,6 @@
     public static final int ITYPE_TOP_DISPLAY_CUTOUT = 12;
     public static final int ITYPE_RIGHT_DISPLAY_CUTOUT = 13;
     public static final int ITYPE_BOTTOM_DISPLAY_CUTOUT = 14;
-    public static final int LAST_ALWAYS_VISIBLE_TYPE = ITYPE_BOTTOM_DISPLAY_CUTOUT;
 
     public static final int ITYPE_LEFT_TAPPABLE_ELEMENT = 15;
     public static final int ITYPE_TOP_TAPPABLE_ELEMENT = 16;
@@ -188,18 +182,6 @@
     }
 
     /**
-     * Mirror the always visible sources from the other state. They will share the same object for
-     * the always visible types.
-     *
-     * @param other the state to mirror the mirrored sources from.
-     */
-    public void mirrorAlwaysVisibleInsetsSources(InsetsState other) {
-        for (int type = FIRST_ALWAYS_VISIBLE_TYPE; type <= LAST_ALWAYS_VISIBLE_TYPE; type++) {
-            mSources[type] = other.mSources[type];
-        }
-    }
-
-    /**
      * Calculates {@link WindowInsets} based on the current source configuration.
      *
      * @param frame The frame to calculate the insets relative to.
@@ -380,14 +362,14 @@
         processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
                 insets, type);
 
-        if (type == MANDATORY_SYSTEM_GESTURES) {
+        if (type == Type.MANDATORY_SYSTEM_GESTURES) {
             // Mandatory system gestures are also system gestures.
             // TODO: find a way to express this more generally. One option would be to define
             //       Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the
             //       ability to set systemGestureInsets() independently from
             //       mandatorySystemGestureInsets() in the Builder.
             processSourceAsPublicType(source, typeInsetsMap, typeSideMap, typeVisibilityMap,
-                    insets, SYSTEM_GESTURES);
+                    insets, Type.SYSTEM_GESTURES);
         }
     }
 
@@ -493,9 +475,14 @@
      * to the client.
      *
      * @param type The {@link InternalInsetsType} of the source to remove
+     * @return {@code true} if this InsetsState was modified; {@code false} otherwise.
      */
-    public void removeSource(@InternalInsetsType int type) {
+    public boolean removeSource(@InternalInsetsType int type) {
+        if (mSources[type] == null) {
+            return false;
+        }
         mSources[type] = null;
+        return true;
     }
 
     /**
@@ -552,6 +539,24 @@
         }
     }
 
+    /**
+     * Sets the values from the other InsetsState. But for sources, only specific types of source
+     * would be set.
+     *
+     * @param other the other InsetsState.
+     * @param types the only types of sources would be set.
+     */
+    public void set(InsetsState other, @InsetsType int types) {
+        mDisplayFrame.set(other.mDisplayFrame);
+        mDisplayCutout.set(other.mDisplayCutout);
+        mRoundedCorners = other.getRoundedCorners();
+        final ArraySet<Integer> t = toInternalType(types);
+        for (int i = t.size() - 1; i >= 0; i--) {
+            final int type = t.valueAt(i);
+            mSources[type] = other.mSources[type];
+        }
+    }
+
     public void addSource(InsetsSource source) {
         mSources[source.getType()] = source;
     }
@@ -575,6 +580,18 @@
         if ((types & Type.CAPTION_BAR) != 0) {
             result.add(ITYPE_CAPTION_BAR);
         }
+        if ((types & Type.SYSTEM_GESTURES) != 0) {
+            result.add(ITYPE_LEFT_GESTURES);
+            result.add(ITYPE_TOP_GESTURES);
+            result.add(ITYPE_RIGHT_GESTURES);
+            result.add(ITYPE_BOTTOM_GESTURES);
+        }
+        if ((types & Type.MANDATORY_SYSTEM_GESTURES) != 0) {
+            result.add(ITYPE_LEFT_MANDATORY_GESTURES);
+            result.add(ITYPE_TOP_MANDATORY_GESTURES);
+            result.add(ITYPE_RIGHT_MANDATORY_GESTURES);
+            result.add(ITYPE_BOTTOM_MANDATORY_GESTURES);
+        }
         if ((types & Type.DISPLAY_CUTOUT) != 0) {
             result.add(ITYPE_LEFT_DISPLAY_CUTOUT);
             result.add(ITYPE_TOP_DISPLAY_CUTOUT);
diff --git a/core/java/android/view/RoundedCorners.java b/core/java/android/view/RoundedCorners.java
index 015e804..569c287 100644
--- a/core/java/android/view/RoundedCorners.java
+++ b/core/java/android/view/RoundedCorners.java
@@ -335,7 +335,7 @@
         }
         if (o instanceof RoundedCorners) {
             RoundedCorners r = (RoundedCorners) o;
-            return Arrays.deepEquals(mRoundedCorners, ((RoundedCorners) o).mRoundedCorners);
+            return Arrays.deepEquals(mRoundedCorners, r.mRoundedCorners);
         }
         return false;
     }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 86968ed..f66c760 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -673,10 +673,6 @@
     // Used in updating override configurations
     private final Configuration mTempConfig = new Configuration();
 
-    // Used in performing layout, to record the insets provided by other windows above the current
-    // window.
-    private InsetsState mTmpAboveInsetsState = new InsetsState();
-
     /**
      * Used to prevent recursions when calling
      * {@link #ensureActivitiesVisible(ActivityRecord, int, boolean, boolean)}
@@ -778,13 +774,6 @@
                     + " parentHidden=" + w.isParentWindowHidden());
         }
 
-        // Sets mAboveInsets for each window. Windows behind the window providing the insets can
-        // receive the insets.
-        if (!w.mAboveInsetsState.equals(mTmpAboveInsetsState)) {
-            w.mAboveInsetsState.set(mTmpAboveInsetsState);
-            mWinInsetsChanged.add(w);
-        }
-
         // If this view is GONE, then skip it -- keep the current frame, and let the caller know
         // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
         // since that means "perform layout as normal, just don't display").
@@ -818,16 +807,8 @@
                     + " mContainingFrame=" + w.getContainingFrame()
                     + " mDisplayFrame=" + w.getDisplayFrame());
         }
-        provideInsetsByWindow(w);
     };
 
-    private void provideInsetsByWindow(WindowState w) {
-        for (int i = 0; i < w.mProvidedInsetsSources.size(); i++) {
-            final InsetsSource providedSource = w.mProvidedInsetsSources.valueAt(i);
-            mTmpAboveInsetsState.addSource(providedSource);
-        }
-    }
-
     private final Consumer<WindowState> mPerformLayoutAttached = w -> {
         if (w.mLayoutAttached) {
             if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
@@ -2511,8 +2492,13 @@
 
     void onDisplayInfoChanged() {
         final DisplayInfo info = mDisplayInfo;
-        mDisplayFrames.onDisplayInfoUpdated(info, calculateDisplayCutoutForRotation(info.rotation),
-                calculateRoundedCornersForRotation(info.rotation));
+        if (mDisplayFrames.onDisplayInfoUpdated(info,
+                calculateDisplayCutoutForRotation(info.rotation),
+                calculateRoundedCornersForRotation(info.rotation))) {
+            // TODO(b/161810301): Set notifyInsetsChange to true while the server no longer performs
+            //                    layout.
+            mInsetsStateController.onDisplayInfoUpdated(false /* notifyInsetsChange */);
+        }
         mInputMonitor.layoutInputConsumers(info.logicalWidth, info.logicalHeight);
         mDisplayPolicy.onDisplayInfoChanged(info);
     }
@@ -3767,8 +3753,11 @@
         // 2. Assign window layers based on the IME surface parent to make sure it is on top of the
         // app.
         assignWindowLayers(true /* setLayoutNeeded */);
-        // 3. Update the IME control target to apply any inset change and animation.
-        // 4. Reparent the IME container surface to either the input target app, or the IME window
+        // 3. The z-order of IME might have been changed. Update the above insets state.
+        mInsetsStateController.updateAboveInsetsState(
+                mInputMethodWindow, true /* notifyInsetsChange */);
+        // 4. Update the IME control target to apply any inset change and animation.
+        // 5. Reparent the IME container surface to either the input target app, or the IME window
         // parent.
         updateImeControlTarget();
     }
@@ -4341,14 +4330,6 @@
                     + " dh=" + mDisplayInfo.logicalHeight);
         }
 
-        // Used to indicate that we have processed the insets windows. This needs to be after
-        // beginLayoutLw to ensure the raw insets state display related info is initialized.
-        final InsetsState rawInsetsState = getInsetsStateController().getRawInsetsState();
-        mTmpAboveInsetsState = new InsetsState();
-        mTmpAboveInsetsState.setDisplayFrame(rawInsetsState.getDisplayFrame());
-        mTmpAboveInsetsState.setDisplayCutout(rawInsetsState.getDisplayCutout());
-        mTmpAboveInsetsState.mirrorAlwaysVisibleInsetsSources(rawInsetsState);
-
         int seq = mLayoutSeq + 1;
         if (seq < 0) seq = 0;
         mLayoutSeq = seq;
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index e4230a2..a37f3f2 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -21,6 +21,7 @@
 import static android.view.InsetsState.ITYPE_RIGHT_DISPLAY_CUTOUT;
 import static android.view.InsetsState.ITYPE_TOP_DISPLAY_CUTOUT;
 
+import android.annotation.NonNull;
 import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayCutout;
@@ -70,25 +71,28 @@
      * @param info the updated {@link DisplayInfo}.
      * @param displayCutout the updated {@link DisplayCutout}.
      * @param roundedCorners the updated {@link RoundedCorners}.
+     * @return {@code true} if the insets state has been changed; {@code false} otherwise.
      */
-    public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout,
-            RoundedCorners roundedCorners) {
-        mDisplayWidth = info.logicalWidth;
-        mDisplayHeight = info.logicalHeight;
+    public boolean onDisplayInfoUpdated(DisplayInfo info, @NonNull WmDisplayCutout displayCutout,
+            @NonNull RoundedCorners roundedCorners) {
         mRotation = info.rotation;
-        final WmDisplayCutout wmDisplayCutout =
-                displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
 
         final InsetsState state = mInsetsState;
-        final Rect unrestricted = mUnrestricted;
         final Rect safe = mDisplayCutoutSafe;
-        final DisplayCutout cutout = wmDisplayCutout.getDisplayCutout();
+        final DisplayCutout cutout = displayCutout.getDisplayCutout();
+        if (mDisplayWidth == info.logicalWidth && mDisplayHeight == info.logicalHeight
+                && state.getDisplayCutout().equals(cutout)
+                && state.getRoundedCorners().equals(roundedCorners)) {
+            return false;
+        }
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
+        final Rect unrestricted = mUnrestricted;
         unrestricted.set(0, 0, mDisplayWidth, mDisplayHeight);
         safe.set(Integer.MIN_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MAX_VALUE);
         state.setDisplayFrame(unrestricted);
         state.setDisplayCutout(cutout);
-        state.setRoundedCorners(roundedCorners != null ? roundedCorners
-                : RoundedCorners.NO_ROUNDED_CORNERS);
+        state.setRoundedCorners(roundedCorners);
         if (!cutout.isEmpty()) {
             if (cutout.getSafeInsetLeft() > 0) {
                 safe.left = unrestricted.left + cutout.getSafeInsetLeft();
@@ -116,6 +120,7 @@
             state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
             state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT);
         }
+        return true;
     }
 
     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 580d328..75176df 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -27,6 +27,9 @@
 import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.WindowInsets.Type.displayCutout;
+import static android.view.WindowInsets.Type.mandatorySystemGestures;
+import static android.view.WindowInsets.Type.systemGestures;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
@@ -293,6 +296,76 @@
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
     }
 
+    /**
+     * Updates {@link WindowState#mAboveInsetsState} for all windows in the display while the
+     * z-order of a window is changed.
+     *
+     * @param win The window whose z-order has changed.
+     * @param notifyInsetsChange {@code true} if the clients should be notified about the change.
+     */
+    void updateAboveInsetsState(WindowState win, boolean notifyInsetsChange) {
+        if (win == null || win.getDisplayContent() != mDisplayContent) {
+            return;
+        }
+        final boolean[] aboveWin = { true };
+        final InsetsState aboveInsetsState = new InsetsState();
+        aboveInsetsState.set(mState,
+                displayCutout() | systemGestures() | mandatorySystemGestures());
+        final SparseArray<InsetsSource> winProvidedSources = win.mProvidedInsetsSources;
+        final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>();
+        mDisplayContent.forAllWindows(w -> {
+            if (aboveWin[0]) {
+                if (w == win) {
+                    aboveWin[0] = false;
+                    if (!win.mAboveInsetsState.equals(aboveInsetsState)) {
+                        win.mAboveInsetsState.set(aboveInsetsState);
+                        insetsChangedWindows.add(win);
+                    }
+                    return winProvidedSources.size() == 0;
+                } else {
+                    final SparseArray<InsetsSource> providedSources = w.mProvidedInsetsSources;
+                    for (int i = providedSources.size() - 1; i >= 0; i--) {
+                        aboveInsetsState.addSource(providedSources.valueAt(i));
+                    }
+                    if (winProvidedSources.size() == 0) {
+                        return false;
+                    }
+                    boolean changed = false;
+                    for (int i = winProvidedSources.size() - 1; i >= 0; i--) {
+                        changed |= w.mAboveInsetsState.removeSource(winProvidedSources.keyAt(i));
+                    }
+                    if (changed) {
+                        insetsChangedWindows.add(w);
+                    }
+                }
+            } else {
+                for (int i = winProvidedSources.size() - 1; i >= 0; i--) {
+                    w.mAboveInsetsState.addSource(winProvidedSources.valueAt(i));
+                }
+                insetsChangedWindows.add(w);
+            }
+            return false;
+        }, true /* traverseTopToBottom */);
+        if (notifyInsetsChange) {
+            for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
+                mDispatchInsetsChanged.accept(insetsChangedWindows.get(i));
+            }
+        }
+    }
+
+    void onDisplayInfoUpdated(boolean notifyInsetsChange) {
+        final ArrayList<WindowState> insetsChangedWindows = new ArrayList<>();
+        mDisplayContent.forAllWindows(w -> {
+            w.mAboveInsetsState.set(mState, displayCutout());
+            insetsChangedWindows.add(w);
+        }, true /* traverseTopToBottom */);
+        if (notifyInsetsChange) {
+            for (int i = insetsChangedWindows.size() - 1; i >= 0; i--) {
+                mDispatchInsetsChanged.accept(insetsChangedWindows.get(i));
+            }
+        }
+    }
+
     void onInsetsModified(InsetsControlTarget caller) {
         boolean changed = false;
         for (int i = mProviders.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 5dc5ab7..99246dd2 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1874,6 +1874,10 @@
                 displayContent.sendNewConfiguration();
             }
 
+            // This window doesn't have a frame yet. Don't let this window cause the insets change.
+            displayContent.getInsetsStateController().updateAboveInsetsState(
+                    win, false /* notifyInsetsChanged */);
+
             getInsetsSourceControls(win, outActiveControls);
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a94b0aa..a8ab01f 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -211,11 +211,11 @@
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
-import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.MergedConfiguration;
 import android.util.Slog;
+import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
@@ -648,12 +648,12 @@
     /**
      * The insets state of sources provided by windows above the current window.
      */
-    InsetsState mAboveInsetsState = new InsetsState();
+    final InsetsState mAboveInsetsState = new InsetsState();
 
     /**
      * The insets sources provided by this window.
      */
-    ArrayMap<Integer, InsetsSource> mProvidedInsetsSources = new ArrayMap<>();
+    final SparseArray<InsetsSource> mProvidedInsetsSources = new SparseArray<>();
 
     /**
      * Surface insets from the previous call to relayout(), used to track
@@ -2204,7 +2204,7 @@
         }
     }
 
-  @Override
+    @Override
     void removeImmediately() {
         super.removeImmediately();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index b1ea4a5..5a0466a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -130,7 +130,7 @@
         // insets state with the global one.
         final InsetsState insetsState =
                 win.getDisplayContent().getInsetsStateController().getRawInsetsState();
-        win.mAboveInsetsState = insetsState;
+        win.mAboveInsetsState.set(insetsState);
     }
 
     public void setRotation(int rotation, boolean includingWindows) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index 2163661..47cf53b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -18,6 +18,7 @@
 
 import static android.view.InsetsState.ITYPE_IME;
 import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
+import static android.view.RoundedCorners.NO_ROUNDED_CORNERS;
 import static android.view.Surface.ROTATION_0;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
@@ -37,6 +38,7 @@
 
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
 import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+import static com.android.server.wm.utils.WmDisplayCutout.NO_CUTOUT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -300,9 +302,9 @@
         displayPolicy.addWindowLw(mNavBarWindow, mNavBarWindow.mAttrs);
         mNavBarWindow.getControllableInsetProvider().setServerVisible(true);
         final InsetsState state = mDisplayContent.getInsetsStateController().getRawInsetsState();
-        mImeWindow.mAboveInsetsState = state;
+        mImeWindow.mAboveInsetsState.set(state);
         mDisplayContent.mDisplayFrames = new DisplayFrames(mDisplayContent.getDisplayId(),
-                state, displayInfo, null /* displayCutout */, null /* roundedCorners*/);
+                state, displayInfo, NO_CUTOUT, NO_ROUNDED_CORNERS);
 
         mDisplayContent.setInputMethodWindowLocked(mImeWindow);
         mImeWindow.mAttrs.setFitInsetsSides(Side.all() & ~Side.BOTTOM);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
index 20775e8..683ed88 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTestsBase.java
@@ -117,7 +117,7 @@
     static Pair<DisplayInfo, WmDisplayCutout> displayInfoAndCutoutForRotation(int rotation,
             boolean withDisplayCutout, boolean isLongEdgeCutout) {
         final DisplayInfo info = new DisplayInfo();
-        WmDisplayCutout cutout = null;
+        WmDisplayCutout cutout = WmDisplayCutout.NO_CUTOUT;
 
         final boolean flippedDimensions = rotation == ROTATION_90 || rotation == ROTATION_270;
         info.logicalWidth = flippedDimensions ? DISPLAY_HEIGHT : DISPLAY_WIDTH;
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index ee293fc..be03603 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,13 +32,14 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.DisplayContent.IME_TARGET_INPUT;
+import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -208,7 +209,7 @@
         app.mAboveInsetsState.getSource(ITYPE_IME).setFrame(mImeWindow.getFrame());
 
         // Make sure app got notified.
-        verify(app, atLeast(1)).notifyInsetsChanged();
+        verify(app, atLeastOnce()).notifyInsetsChanged();
 
         // app will get visible IME insets while below IME.
         assertTrue(getController().getInsetsForWindow(app).getSource(ITYPE_IME).isVisible());
@@ -336,6 +337,92 @@
         assertFalse(app.getInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
     }
 
+    @Test
+    public void testUpdateAboveInsetsState_provideInsets() {
+        final WindowState app = createTestWindow("app");
+        final WindowState statusBar = createTestWindow("statusBar");
+        final WindowState navBar = createTestWindow("navBar");
+
+        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+
+        getController().updateAboveInsetsState(statusBar, true /* notifyInsetsChange */);
+
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    @Test
+    public void testUpdateAboveInsetsState_receiveInsets() {
+        final WindowState app = createTestWindow("app");
+        final WindowState statusBar = createTestWindow("statusBar");
+        final WindowState navBar = createTestWindow("navBar");
+
+        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        getController().updateAboveInsetsState(app, true /* notifyInsetsChange */);
+
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    @Test
+    public void testUpdateAboveInsetsState_zOrderChanged() {
+        final WindowState ime = createTestWindow("ime");
+        final WindowState app = createTestWindow("app");
+        final WindowState statusBar = createTestWindow("statusBar");
+        final WindowState navBar = createTestWindow("navBar");
+
+        getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
+        getController().getSourceProvider(ITYPE_IME).setClientVisible(true);
+        getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
+        getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
+        getController().updateAboveInsetsState(ime, false /* notifyInsetsChange */);
+        getController().updateAboveInsetsState(statusBar, false /* notifyInsetsChange */);
+        getController().updateAboveInsetsState(navBar, false /* notifyInsetsChange */);
+
+        // ime is below others.
+        assertNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNotNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        ime.getParent().positionChildAt(POSITION_TOP, ime, true /* includingParents */);
+        getController().updateAboveInsetsState(ime, true /* notifyInsetsChange */);
+
+        // ime is above others.
+        assertNotNull(app.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(statusBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNotNull(navBar.mAboveInsetsState.peekSource(ITYPE_IME));
+        assertNull(ime.mAboveInsetsState.peekSource(ITYPE_STATUS_BAR));
+        assertNull(ime.mAboveInsetsState.peekSource(ITYPE_NAVIGATION_BAR));
+
+        verify(ime, atLeastOnce()).notifyInsetsChanged();
+        verify(app, atLeastOnce()).notifyInsetsChanged();
+        verify(statusBar, atLeastOnce()).notifyInsetsChanged();
+        verify(navBar, atLeastOnce()).notifyInsetsChanged();
+    }
+
+    private WindowState createTestWindow(String name) {
+        final WindowState win = createWindow(null, TYPE_APPLICATION, name);
+        win.setHasSurface(true);
+        spyOn(win);
+        return win;
+    }
+
     private InsetsStateController getController() {
         return mDisplayContent.getInsetsStateController();
     }