Let System UI show on external displays (1/3)

Let each display have one status bar and one navigation bar. This is
so on each display, status bar and navigation bar can be laid out with
apps and produce proper insets.

Bug: 117474929
Test: atest com.android.server.wm
Test: Watch YouTube video in fullscreen mode, and see if status bar
      and navigation will be hidden as expected. Swipe on the edge
      of screen and see if status bar and navigation bar are both
      shown as expected.

Change-Id: I1550659b7cd1dd1676bf04483c5b68376ef42905
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index c43e64e..5e92b9e 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -2354,7 +2354,7 @@
         // bounds would end up too small.
         outBounds.set(0, 0, maxActivityWidth + appBounds.left, maxActivityHeight + appBounds.top);
 
-        if (service.mWindowManager.getNavBarPosition() == NAV_BAR_LEFT) {
+        if (service.mWindowManager.getNavBarPosition(getDisplayId()) == NAV_BAR_LEFT) {
             // Position the activity frame on the opposite side of the nav bar.
             outBounds.left = appBounds.right - maxActivityWidth;
             outBounds.right = appBounds.right;
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index e65f3b4..32a6f74 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -96,14 +96,15 @@
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");
 
         if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
-        int transit = mDisplayContent.mAppTransition.getAppTransition();
+        final AppTransition appTransition = mDisplayContent.mAppTransition;
+        int transit = appTransition.getAppTransition();
         if (mDisplayContent.mSkipAppTransitionAnimation && !isKeyguardGoingAwayTransit(transit)) {
             transit = WindowManager.TRANSIT_UNSET;
         }
         mDisplayContent.mSkipAppTransitionAnimation = false;
         mDisplayContent.mNoAnimationNotifyOnTransitionFinished.clear();
 
-        mDisplayContent.mAppTransition.removeAppTransitionTimeoutCallbacks();
+        appTransition.removeAppTransitionTimeoutCallbacks();
 
         mDisplayContent.mWallpaperMayChange = false;
 
@@ -141,7 +142,7 @@
         // done behind a dream window.
         final ArraySet<Integer> activityTypes = collectActivityTypes(mDisplayContent.mOpeningApps,
                 mDisplayContent.mClosingApps);
-        final boolean allowAnimations = mService.mPolicy.allowAppAnimationsLw();
+        final boolean allowAnimations = mDisplayContent.getDisplayPolicy().allowAppAnimationsLw();
         final AppWindowToken animLpToken = allowAnimations
                 ? findAnimLayoutParamsToken(transit, activityTypes)
                 : null;
@@ -165,15 +166,15 @@
             handleClosingApps(transit, animLp, voiceInteraction);
             handleOpeningApps(transit, animLp, voiceInteraction);
 
-            mDisplayContent.mAppTransition.setLastAppTransition(transit, topOpeningApp,
+            appTransition.setLastAppTransition(transit, topOpeningApp,
                     topClosingApp);
 
-            final int flags = mDisplayContent.mAppTransition.getTransitFlags();
-            layoutRedo = mDisplayContent.mAppTransition.goodToGo(transit, topOpeningApp,
+            final int flags = appTransition.getTransitFlags();
+            layoutRedo = appTransition.goodToGo(transit, topOpeningApp,
                     topClosingApp, mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps);
             handleNonAppWindowsInTransition(transit, flags);
-            mDisplayContent.mAppTransition.postAnimationCallback();
-            mDisplayContent.mAppTransition.clear();
+            appTransition.postAnimationCallback();
+            appTransition.clear();
         } finally {
             mService.mSurfaceAnimationRunner.continueStartingAnimations();
         }
@@ -254,8 +255,8 @@
     }
 
     /**
-     * @return The set of {@link WindowConfiguration.ActivityType}s contained in the set of apps in
-     *         {@code array1} and {@code array2}.
+     * @return The set of {@link android.app.WindowConfiguration.ActivityType}s contained in the set
+     *         of apps in {@code array1} and {@code array2}.
      */
     private static ArraySet<Integer> collectActivityTypes(ArraySet<AppWindowToken> array1,
             ArraySet<AppWindowToken> array2) {
diff --git a/services/core/java/com/android/server/wm/BarController.java b/services/core/java/com/android/server/wm/BarController.java
new file mode 100644
index 0000000..a335fa2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/BarController.java
@@ -0,0 +1,372 @@
+/*
+ * 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;
+
+import static com.android.server.wm.BarControllerProto.STATE;
+import static com.android.server.wm.BarControllerProto.TRANSIENT_STATE;
+
+import android.app.StatusBarManager;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import java.io.PrintWriter;
+
+/**
+ * Controls state/behavior specific to a system bar window.
+ */
+public class BarController {
+    private static final boolean DEBUG = false;
+
+    private static final int TRANSIENT_BAR_NONE = 0;
+    private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1;
+    private static final int TRANSIENT_BAR_SHOWING = 2;
+    private static final int TRANSIENT_BAR_HIDING = 3;
+
+    private static final int TRANSLUCENT_ANIMATION_DELAY_MS = 1000;
+
+    private static final int MSG_NAV_BAR_VISIBILITY_CHANGED = 1;
+
+    protected final String mTag;
+    private final int mTransientFlag;
+    private final int mUnhideFlag;
+    private final int mTranslucentFlag;
+    private final int mTransparentFlag;
+    private final int mStatusBarManagerId;
+    private final int mTranslucentWmFlag;
+    protected final Handler mHandler;
+    private final Object mServiceAquireLock = new Object();
+    private StatusBarManagerInternal mStatusBarInternal;
+
+    protected WindowState mWin;
+    private int mState = StatusBarManager.WINDOW_STATE_SHOWING;
+    private int mTransientBarState;
+    private boolean mPendingShow;
+    private long mLastTranslucent;
+    private boolean mShowTransparent;
+    private boolean mSetUnHideFlagWhenNextTransparent;
+    private boolean mNoAnimationOnNextShow;
+    private final Rect mContentFrame = new Rect();
+
+    private OnBarVisibilityChangedListener mVisibilityChangeListener;
+
+    BarController(String tag, int transientFlag, int unhideFlag, int translucentFlag,
+            int statusBarManagerId, int translucentWmFlag, int transparentFlag) {
+        mTag = "BarController." + tag;
+        mTransientFlag = transientFlag;
+        mUnhideFlag = unhideFlag;
+        mTranslucentFlag = translucentFlag;
+        mStatusBarManagerId = statusBarManagerId;
+        mTranslucentWmFlag = translucentWmFlag;
+        mTransparentFlag = transparentFlag;
+        mHandler = new BarHandler();
+    }
+
+    void setWindow(WindowState win) {
+        mWin = win;
+    }
+
+    /**
+     * Sets the frame within which the bar will display its content.
+     *
+     * This is used to determine if letterboxes interfere with the display of such content.
+     */
+    void setContentFrame(Rect frame) {
+        mContentFrame.set(frame);
+    }
+
+    void setShowTransparent(boolean transparent) {
+        if (transparent != mShowTransparent) {
+            mShowTransparent = transparent;
+            mSetUnHideFlagWhenNextTransparent = transparent;
+            mNoAnimationOnNextShow = true;
+        }
+    }
+
+    void showTransient() {
+        if (mWin != null) {
+            setTransientBarState(TRANSIENT_BAR_SHOW_REQUESTED);
+        }
+    }
+
+    boolean isTransientShowing() {
+        return mTransientBarState == TRANSIENT_BAR_SHOWING;
+    }
+
+    boolean isTransientShowRequested() {
+        return mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED;
+    }
+
+    boolean wasRecentlyTranslucent() {
+        return (SystemClock.uptimeMillis() - mLastTranslucent) < TRANSLUCENT_ANIMATION_DELAY_MS;
+    }
+
+    void adjustSystemUiVisibilityLw(int oldVis, int vis) {
+        if (mWin != null && mTransientBarState == TRANSIENT_BAR_SHOWING
+                && (vis & mTransientFlag) == 0) {
+            // sysui requests hide
+            setTransientBarState(TRANSIENT_BAR_HIDING);
+            setBarShowingLw(false);
+        } else if (mWin != null && (oldVis & mUnhideFlag) != 0 && (vis & mUnhideFlag) == 0) {
+            // sysui ready to unhide
+            setBarShowingLw(true);
+        }
+    }
+
+    int applyTranslucentFlagLw(WindowState win, int vis, int oldVis) {
+        if (mWin != null) {
+            if (win != null && (win.getAttrs().privateFlags
+                    & WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) == 0) {
+                int fl = PolicyControl.getWindowFlags(win, null);
+                if ((fl & mTranslucentWmFlag) != 0) {
+                    vis |= mTranslucentFlag;
+                } else {
+                    vis &= ~mTranslucentFlag;
+                }
+                if ((fl & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                        && isTransparentAllowed(win)) {
+                    vis |= mTransparentFlag;
+                } else {
+                    vis &= ~mTransparentFlag;
+                }
+            } else {
+                vis = (vis & ~mTranslucentFlag) | (oldVis & mTranslucentFlag);
+                vis = (vis & ~mTransparentFlag) | (oldVis & mTransparentFlag);
+            }
+        }
+        return vis;
+    }
+
+    boolean isTransparentAllowed(WindowState win) {
+        return win == null || !win.isLetterboxedOverlappingWith(mContentFrame);
+    }
+
+    boolean setBarShowingLw(final boolean show) {
+        if (mWin == null) return false;
+        if (show && mTransientBarState == TRANSIENT_BAR_HIDING) {
+            mPendingShow = true;
+            return false;
+        }
+        final boolean wasVis = mWin.isVisibleLw();
+        final boolean wasAnim = mWin.isAnimatingLw();
+        final boolean change = show ? mWin.showLw(!mNoAnimationOnNextShow && !skipAnimation())
+                : mWin.hideLw(!mNoAnimationOnNextShow && !skipAnimation());
+        mNoAnimationOnNextShow = false;
+        final int state = computeStateLw(wasVis, wasAnim, mWin, change);
+        final boolean stateChanged = updateStateLw(state);
+
+        if (change && (mVisibilityChangeListener != null)) {
+            mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED, show ? 1 : 0, 0).sendToTarget();
+        }
+
+        return change || stateChanged;
+    }
+
+    void setOnBarVisibilityChangedListener(OnBarVisibilityChangedListener listener,
+            boolean invokeWithState) {
+        mVisibilityChangeListener = listener;
+        if (invokeWithState) {
+            // Optionally report the initial window state for initialization purposes
+            mHandler.obtainMessage(MSG_NAV_BAR_VISIBILITY_CHANGED,
+                    (mState == StatusBarManager.WINDOW_STATE_SHOWING) ? 1 : 0, 0).sendToTarget();
+        }
+    }
+
+    protected boolean skipAnimation() {
+        return !mWin.isDrawnLw();
+    }
+
+    private int computeStateLw(boolean wasVis, boolean wasAnim, WindowState win, boolean change) {
+        if (win.isDrawnLw()) {
+            final boolean vis = win.isVisibleLw();
+            final boolean anim = win.isAnimatingLw();
+            if (mState == StatusBarManager.WINDOW_STATE_HIDING && !change && !vis) {
+                return StatusBarManager.WINDOW_STATE_HIDDEN;
+            } else if (mState == StatusBarManager.WINDOW_STATE_HIDDEN && vis) {
+                return StatusBarManager.WINDOW_STATE_SHOWING;
+            } else if (change) {
+                if (wasVis && vis && !wasAnim && anim) {
+                    return StatusBarManager.WINDOW_STATE_HIDING;
+                } else {
+                    return StatusBarManager.WINDOW_STATE_SHOWING;
+                }
+            }
+        }
+        return mState;
+    }
+
+    private boolean updateStateLw(final int state) {
+        if (state != mState) {
+            mState = state;
+            if (DEBUG) Slog.d(mTag, "mState: " + StatusBarManager.windowStateToString(state));
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    StatusBarManagerInternal statusbar = getStatusBarInternal();
+                    if (statusbar != null) {
+                        statusbar.setWindowState(mWin.getDisplayId(), mStatusBarManagerId, state);
+                    }
+                }
+            });
+            return true;
+        }
+        return false;
+    }
+
+    boolean checkHiddenLw() {
+        if (mWin != null && mWin.isDrawnLw()) {
+            if (!mWin.isVisibleLw() && !mWin.isAnimatingLw()) {
+                updateStateLw(StatusBarManager.WINDOW_STATE_HIDDEN);
+            }
+            if (mTransientBarState == TRANSIENT_BAR_HIDING && !mWin.isVisibleLw()) {
+                // Finished animating out, clean up and reset style
+                setTransientBarState(TRANSIENT_BAR_NONE);
+                if (mPendingShow) {
+                    setBarShowingLw(true);
+                    mPendingShow = false;
+                }
+                return true;
+            }
+        }
+        return false;
+    }
+
+    boolean checkShowTransientBarLw() {
+        if (mTransientBarState == TRANSIENT_BAR_SHOWING) {
+            if (DEBUG) Slog.d(mTag, "Not showing transient bar, already shown");
+            return false;
+        } else if (mTransientBarState == TRANSIENT_BAR_SHOW_REQUESTED) {
+            if (DEBUG) Slog.d(mTag, "Not showing transient bar, already requested");
+            return false;
+        } else if (mWin == null) {
+            if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar doesn't exist");
+            return false;
+        } else if (mWin.isDisplayedLw()) {
+            if (DEBUG) Slog.d(mTag, "Not showing transient bar, bar already visible");
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    int updateVisibilityLw(boolean transientAllowed, int oldVis, int vis) {
+        if (mWin == null) return vis;
+        if (isTransientShowing() || isTransientShowRequested()) { // transient bar requested
+            if (transientAllowed) {
+                vis |= mTransientFlag;
+                if ((oldVis & mTransientFlag) == 0) {
+                    vis |= mUnhideFlag;  // tell sysui we're ready to unhide
+                }
+                setTransientBarState(TRANSIENT_BAR_SHOWING);  // request accepted
+            } else {
+                setTransientBarState(TRANSIENT_BAR_NONE);  // request denied
+            }
+        }
+        if (mShowTransparent) {
+            vis |= mTransparentFlag;
+            if (mSetUnHideFlagWhenNextTransparent) {
+                vis |= mUnhideFlag;
+                mSetUnHideFlagWhenNextTransparent = false;
+            }
+        }
+        if (mTransientBarState != TRANSIENT_BAR_NONE) {
+            vis |= mTransientFlag;  // ignore clear requests until transition completes
+            vis &= ~View.SYSTEM_UI_FLAG_LOW_PROFILE;  // never show transient bars in low profile
+        }
+        if ((vis & mTranslucentFlag) != 0 || (oldVis & mTranslucentFlag) != 0
+                || ((vis | oldVis) & mTransparentFlag) != 0) {
+            mLastTranslucent = SystemClock.uptimeMillis();
+        }
+        return vis;
+    }
+
+    private void setTransientBarState(int state) {
+        if (mWin != null && state != mTransientBarState) {
+            if (mTransientBarState == TRANSIENT_BAR_SHOWING || state == TRANSIENT_BAR_SHOWING) {
+                mLastTranslucent = SystemClock.uptimeMillis();
+            }
+            mTransientBarState = state;
+            if (DEBUG) Slog.d(mTag, "mTransientBarState: " + transientBarStateToString(state));
+        }
+    }
+
+    protected StatusBarManagerInternal getStatusBarInternal() {
+        synchronized (mServiceAquireLock) {
+            if (mStatusBarInternal == null) {
+                mStatusBarInternal = LocalServices.getService(StatusBarManagerInternal.class);
+            }
+            return mStatusBarInternal;
+        }
+    }
+
+    private static String transientBarStateToString(int state) {
+        if (state == TRANSIENT_BAR_HIDING) return "TRANSIENT_BAR_HIDING";
+        if (state == TRANSIENT_BAR_SHOWING) return "TRANSIENT_BAR_SHOWING";
+        if (state == TRANSIENT_BAR_SHOW_REQUESTED) return "TRANSIENT_BAR_SHOW_REQUESTED";
+        if (state == TRANSIENT_BAR_NONE) return "TRANSIENT_BAR_NONE";
+        throw new IllegalArgumentException("Unknown state " + state);
+    }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(STATE, mState);
+        proto.write(TRANSIENT_STATE, mTransientBarState);
+        proto.end(token);
+    }
+
+    void dump(PrintWriter pw, String prefix) {
+        if (mWin != null) {
+            pw.print(prefix); pw.println(mTag);
+            pw.print(prefix); pw.print("  "); pw.print("mState"); pw.print('=');
+            pw.println(StatusBarManager.windowStateToString(mState));
+            pw.print(prefix); pw.print("  "); pw.print("mTransientBar"); pw.print('=');
+            pw.println(transientBarStateToString(mTransientBarState));
+            pw.print(prefix); pw.print("  mContentFrame="); pw.println(mContentFrame);
+        }
+    }
+
+    private class BarHandler extends Handler {
+        BarHandler() {
+            super(UiThread.getHandler().getLooper());
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_NAV_BAR_VISIBILITY_CHANGED:
+                    final boolean visible = msg.arg1 != 0;
+                    if (mVisibilityChangeListener != null) {
+                        mVisibilityChangeListener.onBarVisibilityChanged(visible);
+                    }
+                    break;
+            }
+        }
+    }
+
+    interface OnBarVisibilityChangedListener {
+        void onBarVisibilityChanged(boolean visible);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 2f7956e..886b2ff 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -156,6 +156,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.SurfaceSession;
+import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
@@ -182,9 +183,6 @@
 /**
  * Utility class for keeping track of the WindowStates and other pertinent contents of a
  * particular Display.
- *
- * IMPORTANT: No method from this class should ever be used without holding
- * WindowManagerService.mWindowMap.
  */
 class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
         implements WindowManagerPolicy.DisplayContentInfo {
@@ -289,7 +287,8 @@
      * @see WindowManagerService#createWatermarkInTransaction()
      */
     final DisplayMetrics mRealDisplayMetrics = new DisplayMetrics();
-    /** @see #computeCompatSmallestWidth(boolean, int, int, int, int) */
+
+    /** @see #computeCompatSmallestWidth(boolean, int, int, int, DisplayCutout) */
     private final DisplayMetrics mTmpDisplayMetrics = new DisplayMetrics();
 
     /**
@@ -508,6 +507,11 @@
 
     private final PointerEventDispatcher mPointerEventDispatcher;
 
+    // Last systemUiVisibility we received from status bar.
+    private int mLastStatusBarVisibility = 0;
+    // Last systemUiVisibility we dispatched to windows.
+    private int mLastDispatchedSystemUiVisibility = 0;
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         final AppWindowToken atoken = w.mAppToken;
@@ -649,7 +653,7 @@
                 w.mLayoutNeeded = false;
                 w.prelayout();
                 final boolean firstLayout = !w.isLaidOut();
-                mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
+                getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
                 w.mLayoutSeq = mLayoutSeq;
 
                 // If this is the first layout, we need to initialize the last inset values as
@@ -688,7 +692,7 @@
                 }
                 w.mLayoutNeeded = false;
                 w.prelayout();
-                mService.mPolicy.layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
+                getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
                 w.mLayoutSeq = mLayoutSeq;
                 if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrameLw()
                         + " mContainingFrame=" + w.getContainingFrame()
@@ -708,7 +712,7 @@
     };
 
     private final Consumer<WindowState> mApplyPostLayoutPolicy =
-            w -> mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
+            w -> getDisplayPolicy().applyPostLayoutPolicyLw(w, w.mAttrs, w.getParentWindow(),
                     mInputMethodTarget);
 
     private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
@@ -838,15 +842,6 @@
         mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo,
                 calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
         initializeDisplayBaseInfo();
-        mDisplayPolicy = new DisplayPolicy(service);
-        mDisplayRotation = new DisplayRotation(service, this);
-        if (isDefaultDisplay) {
-            // The policy may be invoked right after here, so it requires the necessary default
-            // fields of this display content.
-            mService.mPolicy.setDefaultDisplay(this);
-        }
-        mDividerControllerLocked = new DockedStackDividerController(service, this);
-        mPinnedStackControllerLocked = new PinnedStackController(service, this);
 
         mAppTransition = new AppTransition(service.mContext, service, this);
         mAppTransition.registerListenerLocked(service.mActivityManagerAppTransitionNotifier);
@@ -857,6 +852,30 @@
         mBoundsAnimationController = new BoundsAnimationController(service.mContext,
                 mAppTransition, SurfaceAnimationThread.getHandler(), animationHandler);
 
+        if (mService.mInputManager != null) {
+            final InputChannel inputChannel = mService.mInputManager.monitorInput("Display "
+                    + mDisplayId, mDisplayId);
+            mPointerEventDispatcher = inputChannel != null
+                    ? new PointerEventDispatcher(inputChannel) : null;
+        } else {
+            mPointerEventDispatcher = null;
+        }
+        mDisplayPolicy = new DisplayPolicy(service, this);
+        mDisplayRotation = new DisplayRotation(service, this);
+        if (isDefaultDisplay) {
+            // The policy may be invoked right after here, so it requires the necessary default
+            // fields of this display content.
+            mService.mPolicy.setDefaultDisplay(this);
+        }
+        if (mService.mDisplayReady) {
+            mDisplayPolicy.onConfigurationChanged();
+        }
+        if (mService.mSystemReady) {
+            mDisplayPolicy.systemReady();
+        }
+        mDividerControllerLocked = new DockedStackDividerController(service, this);
+        mPinnedStackControllerLocked = new PinnedStackController(service, this);
+
         // We use this as our arbitrary surface size for buffer-less parents
         // that don't impose cropping on their children. It may need to be larger
         // than the display size because fullscreen windows can be shifted offscreen
@@ -895,15 +914,6 @@
 
         mService.mAnimator.addDisplayLocked(mDisplayId);
         mInputMonitor = new InputMonitor(service, mDisplayId);
-
-        if (mService.mInputManager != null) {
-            final InputChannel inputChannel = mService.mInputManager.monitorInput("Display "
-                    + mDisplayId, mDisplayId);
-            mPointerEventDispatcher = inputChannel != null
-                    ? new PointerEventDispatcher(inputChannel) : null;
-        } else {
-            mPointerEventDispatcher = null;
-        }
     }
 
     boolean isReady() {
@@ -1223,7 +1233,7 @@
         if (DEBUG_ORIENTATION) Slog.v(TAG_WM, "Computed rotation=" + rotation + " for display id="
                 + mDisplayId + " based on lastOrientation=" + lastOrientation
                 + " and oldRotation=" + oldRotation);
-        boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(mDisplayRotation,
+        boolean mayRotateSeamlessly = mDisplayPolicy.shouldRotateSeamlessly(mDisplayRotation,
                 oldRotation, rotation);
 
         if (mayRotateSeamlessly) {
@@ -1286,7 +1296,7 @@
 
         setLayoutNeeded();
         final int[] anim = new int[2];
-        mService.mPolicy.selectRotationAnimationLw(anim);
+        mDisplayPolicy.selectRotationAnimationLw(anim);
 
         if (!rotateSeamlessly) {
             mService.startFreezingDisplayLocked(anim[0], anim[1], this);
@@ -1445,10 +1455,10 @@
         final WmDisplayCutout wmDisplayCutout = calculateDisplayCutoutForRotation(mRotation);
         final DisplayCutout displayCutout = wmDisplayCutout.getDisplayCutout();
 
-        final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
-                mDisplayId, displayCutout);
-        final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
-                mDisplayId, displayCutout);
+        final int appWidth = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
+                displayCutout);
+        final int appHeight = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
+                displayCutout);
         mDisplayInfo.rotation = mRotation;
         mDisplayInfo.logicalWidth = dw;
         mDisplayInfo.logicalHeight = dh;
@@ -1527,13 +1537,13 @@
 
         final float density = mDisplayMetrics.density;
         config.screenWidthDp =
-                (int)(mService.mPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation,
-                        config.uiMode, mDisplayId, displayInfo.displayCutout) / density);
+                (int)(mDisplayPolicy.getConfigDisplayWidth(dw, dh, displayInfo.rotation,
+                        config.uiMode, displayInfo.displayCutout) / density);
         config.screenHeightDp =
-                (int)(mService.mPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation,
-                        config.uiMode, mDisplayId, displayInfo.displayCutout) / density);
+                (int)(mDisplayPolicy.getConfigDisplayHeight(dw, dh, displayInfo.rotation,
+                        config.uiMode, displayInfo.displayCutout) / density);
 
-        mService.mPolicy.getNonDecorInsetsLw(displayInfo.rotation, dw, dh,
+        mDisplayPolicy.getNonDecorInsetsLw(displayInfo.rotation, dw, dh,
                 displayInfo.displayCutout, mTmpRect);
         final int leftInset = mTmpRect.left;
         final int topInset = mTmpRect.top;
@@ -1544,8 +1554,8 @@
         final boolean rotated = (displayInfo.rotation == Surface.ROTATION_90
                 || displayInfo.rotation == Surface.ROTATION_270);
 
-        computeSizeRangesAndScreenLayout(displayInfo, mDisplayId, rotated, config.uiMode, dw, dh,
-                density, config);
+        computeSizeRangesAndScreenLayout(displayInfo, rotated, config.uiMode, dw, dh, density,
+                config);
 
         config.screenLayout = (config.screenLayout & ~Configuration.SCREENLAYOUT_ROUND_MASK)
                 | ((displayInfo.flags & Display.FLAG_ROUND) != 0
@@ -1555,7 +1565,7 @@
         config.compatScreenWidthDp = (int)(config.screenWidthDp / mCompatibleScreenScale);
         config.compatScreenHeightDp = (int)(config.screenHeightDp / mCompatibleScreenScale);
         config.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, config.uiMode, dw,
-                dh, displayInfo.displayCutout, mDisplayId);
+                dh, displayInfo.displayCutout);
         config.densityDpi = displayInfo.logicalDensityDpi;
 
         config.colorMode =
@@ -1624,6 +1634,8 @@
             mService.mH.sendEmptyMessage(WindowManagerService.H.REPORT_HARD_KEYBOARD_STATUS_CHANGE);
         }
 
+        mDisplayPolicy.updateConfigurationDependentBehaviors();
+
         // Let the policy update hidden states.
         config.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
         config.hardKeyboardHidden = Configuration.HARDKEYBOARDHIDDEN_NO;
@@ -1632,7 +1644,7 @@
     }
 
     private int computeCompatSmallestWidth(boolean rotated, int uiMode, int dw, int dh,
-            DisplayCutout displayCutout, int displayId) {
+            DisplayCutout displayCutout) {
         mTmpDisplayMetrics.setTo(mDisplayMetrics);
         final DisplayMetrics tmpDm = mTmpDisplayMetrics;
         final int unrotDw, unrotDh;
@@ -1644,22 +1656,22 @@
             unrotDh = dh;
         }
         int sw = reduceCompatConfigWidthSize(0, Surface.ROTATION_0, uiMode, tmpDm, unrotDw, unrotDh,
-                displayCutout, displayId);
+                displayCutout);
         sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_90, uiMode, tmpDm, unrotDh, unrotDw,
-                displayCutout, displayId);
+                displayCutout);
         sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_180, uiMode, tmpDm, unrotDw, unrotDh,
-                displayCutout, displayId);
+                displayCutout);
         sw = reduceCompatConfigWidthSize(sw, Surface.ROTATION_270, uiMode, tmpDm, unrotDh, unrotDw,
-                displayCutout, displayId);
+                displayCutout);
         return sw;
     }
 
     private int reduceCompatConfigWidthSize(int curSize, int rotation, int uiMode,
-            DisplayMetrics dm, int dw, int dh, DisplayCutout displayCutout, int displayId) {
-        dm.noncompatWidthPixels = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
-                displayId, displayCutout);
-        dm.noncompatHeightPixels = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
-                uiMode, displayId, displayCutout);
+            DisplayMetrics dm, int dw, int dh, DisplayCutout displayCutout) {
+        dm.noncompatWidthPixels = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode,
+                displayCutout);
+        dm.noncompatHeightPixels = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode,
+                displayCutout);
         float scale = CompatibilityInfo.computeCompatibleScaling(dm, null);
         int size = (int)(((dm.noncompatWidthPixels / scale) / dm.density) + .5f);
         if (curSize == 0 || size < curSize) {
@@ -1668,8 +1680,8 @@
         return curSize;
     }
 
-    private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, int displayId,
-            boolean rotated, int uiMode, int dw, int dh, float density, Configuration outConfig) {
+    private void computeSizeRangesAndScreenLayout(DisplayInfo displayInfo, boolean rotated,
+            int uiMode, int dw, int dh, float density, Configuration outConfig) {
 
         // We need to determine the smallest width that will occur under normal
         // operation.  To this, start with the base screen size and compute the
@@ -1687,34 +1699,28 @@
         displayInfo.smallestNominalAppHeight = 1<<30;
         displayInfo.largestNominalAppWidth = 0;
         displayInfo.largestNominalAppHeight = 0;
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_0, uiMode, unrotDw,
-                unrotDh);
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_90, uiMode, unrotDh,
-                unrotDw);
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_180, uiMode, unrotDw,
-                unrotDh);
-        adjustDisplaySizeRanges(displayInfo, displayId, Surface.ROTATION_270, uiMode, unrotDh,
-                unrotDw);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_0, uiMode, unrotDw, unrotDh);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_90, uiMode, unrotDh, unrotDw);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_180, uiMode, unrotDw, unrotDh);
+        adjustDisplaySizeRanges(displayInfo, Surface.ROTATION_270, uiMode, unrotDh, unrotDw);
         int sl = Configuration.resetScreenLayout(outConfig.screenLayout);
         sl = reduceConfigLayout(sl, Surface.ROTATION_0, density, unrotDw, unrotDh, uiMode,
-                displayInfo.displayCutout, displayId);
+                displayInfo.displayCutout);
         sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode,
-                displayInfo.displayCutout, displayId);
+                displayInfo.displayCutout);
         sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode,
-                displayInfo.displayCutout, displayId);
+                displayInfo.displayCutout);
         sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode,
-                displayInfo.displayCutout, displayId);
+                displayInfo.displayCutout);
         outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
         outConfig.screenLayout = sl;
     }
 
     private int reduceConfigLayout(int curLayout, int rotation, float density, int dw, int dh,
-            int uiMode, DisplayCutout displayCutout, int displayId) {
+            int uiMode, DisplayCutout displayCutout) {
         // Get the app screen size at this rotation.
-        int w = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayId,
-                displayCutout);
-        int h = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayId,
-                displayCutout);
+        int w = mDisplayPolicy.getNonDecorDisplayWidth(dw, dh, rotation, uiMode, displayCutout);
+        int h = mDisplayPolicy.getNonDecorDisplayHeight(dw, dh, rotation, uiMode, displayCutout);
 
         // Compute the screen layout size class for this rotation.
         int longSize = w;
@@ -1729,20 +1735,20 @@
         return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
     }
 
-    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int displayId, int rotation,
+    private void adjustDisplaySizeRanges(DisplayInfo displayInfo, int rotation,
             int uiMode, int dw, int dh) {
         final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(
                 rotation).getDisplayCutout();
-        final int width = mService.mPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
-                displayId, displayCutout);
+        final int width = mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation, uiMode,
+                displayCutout);
         if (width < displayInfo.smallestNominalAppWidth) {
             displayInfo.smallestNominalAppWidth = width;
         }
         if (width > displayInfo.largestNominalAppWidth) {
             displayInfo.largestNominalAppWidth = width;
         }
-        final int height = mService.mPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
-                displayId, displayCutout);
+        final int height = mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation, uiMode,
+                displayCutout);
         if (height < displayInfo.smallestNominalAppHeight) {
             displayInfo.smallestNominalAppHeight = height;
         }
@@ -1887,6 +1893,9 @@
     @Override
     public void onConfigurationChanged(Configuration newParentConfig) {
         super.onConfigurationChanged(newParentConfig);
+        if (mDisplayPolicy != null) {
+            mDisplayPolicy.onConfigurationChanged();
+        }
 
         // If there was no pinned stack, we still need to notify the controller of the display info
         // update as a result of the config change.
@@ -2383,7 +2392,7 @@
             mDisplayReady = false;
             mRemovingDisplay = false;
         }
-
+        mDisplayPolicy.onDisplayRemoved();
         mInputMonitor.onRemoved();
         mService.mWindowPlacerLocked.requestTraversal();
     }
@@ -2659,6 +2668,10 @@
             }
         }
         pw.print("  mFocusedApp="); pw.println(mFocusedApp);
+        if (mLastStatusBarVisibility != 0) {
+            pw.print("  mLastStatusBarVisibility=0x");
+            pw.println(Integer.toHexString(mLastStatusBarVisibility));
+        }
 
         pw.println();
         mWallpaperController.dump(pw, "  ");
@@ -2852,9 +2865,7 @@
             }
         }
 
-        // System UI is only shown on the default display.
-        int focusChanged = isDefaultDisplay
-                ? mService.mPolicy.focusChangedLw(oldFocus, newFocus) : 0;
+        int focusChanged = getDisplayPolicy().focusChangedLw(oldFocus, newFocus);
 
         if (imWindowChanged && oldFocus != mInputMethodWindow) {
             // Focus of the input method window changed. Perform layout if needed.
@@ -3321,6 +3332,31 @@
         return win != null;
     }
 
+    void statusBarVisibilityChanged(int visibility) {
+        mLastStatusBarVisibility = visibility;
+        visibility = getDisplayPolicy().adjustSystemUiVisibilityLw(visibility);
+        updateStatusBarVisibilityLocked(visibility);
+    }
+
+    private boolean updateStatusBarVisibilityLocked(int visibility) {
+        if (mLastDispatchedSystemUiVisibility == visibility) {
+            return false;
+        }
+        final int globalDiff = (visibility ^ mLastDispatchedSystemUiVisibility)
+                // We are only interested in differences of one of the
+                // clearable flags...
+                & View.SYSTEM_UI_CLEARABLE_FLAGS
+                // ...if it has actually been cleared.
+                & ~visibility;
+
+        mLastDispatchedSystemUiVisibility = visibility;
+        if (isDefaultDisplay) {
+            mService.mInputManager.setSystemUiVisibility(visibility);
+        }
+        updateSystemUiVisibility(visibility, globalDiff);
+        return true;
+    }
+
     void updateSystemUiVisibility(int visibility, int globalDiff) {
         forAllWindows(w -> {
             try {
@@ -3341,6 +3377,13 @@
         }, true /* traverseTopToBottom */);
     }
 
+    void reevaluateStatusBarVisibility() {
+        int visibility = getDisplayPolicy().adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
+        if (updateStatusBarVisibilityLocked(visibility)) {
+            mService.mWindowPlacerLocked.requestTraversal();
+        }
+    }
+
     void onWindowFreezeTimeout() {
         Slog.w(TAG_WM, "Window freeze timeout expired.");
         mService.mWindowsFreezingScreen = WINDOWS_FREEZING_SCREENS_TIMEOUT;
@@ -3372,9 +3415,6 @@
 
     // TODO: Super crazy long method that should be broken down...
     void applySurfaceChangesTransaction(boolean recoveringMemory) {
-
-        final int dw = mDisplayInfo.logicalWidth;
-        final int dh = mDisplayInfo.logicalHeight;
         final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;
 
         mTmpUpdateAllDrawn.clear();
@@ -3417,13 +3457,11 @@
             // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think it is animating.
             pendingLayoutChanges = 0;
 
-            if (isDefaultDisplay) {
-                mService.mPolicy.beginPostLayoutPolicyLw(dw, dh);
-                forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
-                pendingLayoutChanges |= mService.mPolicy.finishPostLayoutPolicyLw();
-                if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
-                        "after finishPostLayoutPolicyLw", pendingLayoutChanges);
-            }
+            mDisplayPolicy.beginPostLayoutPolicyLw();
+            forAllWindows(mApplyPostLayoutPolicy, true /* traverseTopToBottom */);
+            pendingLayoutChanges |= mDisplayPolicy.finishPostLayoutPolicyLw();
+            if (DEBUG_LAYOUT_REPEATS) surfacePlacer.debugLayoutRepeats(
+                    "after finishPostLayoutPolicyLw", pendingLayoutChanges);
         } while (pendingLayoutChanges != 0);
 
         mTmpApplySurfaceChangesTransactionState.reset();
@@ -3513,12 +3551,7 @@
         // TODO: Not sure if we really need to set the rotation here since we are updating from the
         // display info above...
         mDisplayFrames.mRotation = mRotation;
-        mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
-        if (isDefaultDisplay) {
-            // Not needed on non-default displays.
-            mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
-            mService.mScreenRect.set(0, 0, dw, dh);
-        }
+        mDisplayPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
 
         int seq = mLayoutSeq + 1;
         if (seq < 0) seq = 0;
@@ -4548,7 +4581,7 @@
      * However we need child windows of the applications to be above the IME (Text drag handles).
      * This is a non-strictly hierarcical layering and we need to break out of the Z ordering
      * somehow. We do this by relatively ordering children of the target to the IME in cooperation
-     * with {@link #WindowState#assignLayer}
+     * with {@link WindowState#assignLayer}
      */
     void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
         child.assignRelativeLayer(t, mImeWindowsContainers.getSurfaceControl(), 1);
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 9151ddf..c16f95e 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -16,20 +16,143 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.res.Configuration.UI_MODE_TYPE_CAR;
+import static android.content.res.Configuration.UI_MODE_TYPE_MASK;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowManager.INPUT_CONSUMER_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
+import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
+import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
+import static android.view.WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_ATTACHED_IN_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_SCREEN_DECOR;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_JUMPCUT;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+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_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
 import static android.view.WindowManagerPolicyConstants.EXTRA_HDMI_PLUGGED_STATE;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
+
+import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+import static com.android.server.wm.ActivityTaskManagerInternal.SleepToken;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.localLOGV;
 
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.app.StatusBarManager;
+import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.hardware.input.InputManager;
+import android.hardware.power.V1_0.PowerHint;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
+import android.view.DisplayCutout;
+import android.view.Gravity;
+import android.view.IApplicationToken;
+import android.view.InputChannel;
+import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicyConstants;
+import android.view.accessibility.AccessibilityManager;
 
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ScreenShapeHelper;
+import com.android.internal.util.ScreenshotHelper;
+import com.android.server.LocalServices;
+import com.android.server.UiThread;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.policy.WindowManagerPolicy.InputConsumer;
+import com.android.server.policy.WindowManagerPolicy.NavigationBarPosition;
 import com.android.server.policy.WindowManagerPolicy.ScreenOnListener;
 import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
+import com.android.server.policy.WindowOrientationListener;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.utils.InsetUtils;
 
 import java.io.PrintWriter;
 
@@ -38,12 +161,61 @@
  */
 public class DisplayPolicy {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplayPolicy" : TAG_WM;
+    private static final boolean DEBUG = false;
+
+    private static final boolean ALTERNATE_CAR_MODE_NAV_SIZE = false;
+
+    // The panic gesture may become active only after the keyguard is dismissed and the immersive
+    // app shows again. If that doesn't happen for 30s we drop the gesture.
+    private static final long PANIC_GESTURE_EXPIRATION = 30000;
+
+    // Controls navigation bar opacity depending on which workspace stacks are currently
+    // visible.
+    // Nav bar is always opaque when either the freeform stack or docked stack is visible.
+    private static final int NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED = 0;
+    // Nav bar is always translucent when the freeform stack is visible, otherwise always opaque.
+    private static final int NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE = 1;
+
+    /**
+     * These are the system UI flags that, when changing, can cause the layout
+     * of the screen to change.
+     */
+    private static final int SYSTEM_UI_CHANGING_LAYOUT =
+            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_FULLSCREEN
+                    | View.STATUS_BAR_TRANSLUCENT
+                    | View.NAVIGATION_BAR_TRANSLUCENT
+                    | View.STATUS_BAR_TRANSPARENT
+                    | View.NAVIGATION_BAR_TRANSPARENT;
 
     private final WindowManagerService mService;
+    private final Context mContext;
+    private final DisplayContent mDisplayContent;
     private final Object mLock;
+    private final Handler mHandler;
 
     private final boolean mCarDockEnablesAccelerometer;
     private final boolean mDeskDockEnablesAccelerometer;
+    private final boolean mTranslucentDecorEnabled;
+    private final AccessibilityManager mAccessibilityManager;
+    private final ImmersiveModeConfirmation mImmersiveModeConfirmation;
+    private final ScreenshotHelper mScreenshotHelper;
+
+    private final Object mServiceAcquireLock = new Object();
+    private StatusBarManagerInternal mStatusBarManagerInternal;
+
+    private StatusBarManagerInternal getStatusBarManagerInternal() {
+        synchronized (mServiceAcquireLock) {
+            if (mStatusBarManagerInternal == null) {
+                mStatusBarManagerInternal =
+                        LocalServices.getService(StatusBarManagerInternal.class);
+            }
+            return mStatusBarManagerInternal;
+        }
+    }
+
+    @VisibleForTesting
+    private final SystemGesturesPointerEventListener mSystemGestures;
 
     private volatile int mLidState = LID_ABSENT;
     private volatile int mDockMode = Intent.EXTRA_DOCK_STATE_UNDOCKED;
@@ -64,32 +236,311 @@
     private volatile boolean mKeyguardDrawComplete;
     private volatile boolean mWindowManagerDrawComplete;
 
-    DisplayPolicy(WindowManagerService service) {
+    private final ArraySet<WindowState> mScreenDecorWindows = new ArraySet<>();
+    private WindowState mStatusBar = null;
+    private final int[] mStatusBarHeightForRotation = new int[4];
+    private WindowState mNavigationBar = null;
+    @NavigationBarPosition
+    private int mNavigationBarPosition = NAV_BAR_BOTTOM;
+    private int[] mNavigationBarHeightForRotationDefault = new int[4];
+    private int[] mNavigationBarWidthForRotationDefault = new int[4];
+    private int[] mNavigationBarHeightForRotationInCarMode = new int[4];
+    private int[] mNavigationBarWidthForRotationInCarMode = new int[4];
+
+    private final StatusBarController mStatusBarController = new StatusBarController();
+
+    private final BarController mNavigationBarController = new BarController("NavigationBar",
+            View.NAVIGATION_BAR_TRANSIENT,
+            View.NAVIGATION_BAR_UNHIDE,
+            View.NAVIGATION_BAR_TRANSLUCENT,
+            StatusBarManager.WINDOW_NAVIGATION_BAR,
+            FLAG_TRANSLUCENT_NAVIGATION,
+            View.NAVIGATION_BAR_TRANSPARENT);
+
+    private final BarController.OnBarVisibilityChangedListener mNavBarVisibilityListener =
+            new BarController.OnBarVisibilityChangedListener() {
+                @Override
+                public void onBarVisibilityChanged(boolean visible) {
+                    if (mAccessibilityManager == null) {
+                        return;
+                    }
+                    mAccessibilityManager.notifyAccessibilityButtonVisibilityChanged(visible);
+                }
+            };
+
+    // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+    private NavigationBarExperiments mExperiments = new NavigationBarExperiments();
+    // EXPERIMENT END
+
+    @GuardedBy("mHandler")
+    private SleepToken mDreamingSleepToken;
+
+    @GuardedBy("mHandler")
+    private SleepToken mWindowSleepToken;
+
+    private final Runnable mAcquireSleepTokenRunnable;
+    private final Runnable mReleaseSleepTokenRunnable;
+
+    // The windows we were told about in focusChanged.
+    private WindowState mFocusedWindow;
+    private WindowState mLastFocusedWindow;
+
+    IApplicationToken mFocusedApp;
+
+    int mLastSystemUiFlags;
+    // Bits that we are in the process of clearing, so we want to prevent
+    // them from being set by applications until everything has been updated
+    // to have them clear.
+    private int mResettingSystemUiFlags = 0;
+    // Bits that we are currently always keeping cleared.
+    private int mForceClearedSystemUiFlags = 0;
+    private int mLastFullscreenStackSysUiFlags;
+    private int mLastDockedStackSysUiFlags;
+    private final Rect mNonDockedStackBounds = new Rect();
+    private final Rect mDockedStackBounds = new Rect();
+    private final Rect mLastNonDockedStackBounds = new Rect();
+    private final Rect mLastDockedStackBounds = new Rect();
+
+    // What we last reported to system UI about whether the compatibility
+    // menu needs to be displayed.
+    private boolean mLastFocusNeedsMenu = false;
+    // If nonzero, a panic gesture was performed at that time in uptime millis and is still pending.
+    private long mPendingPanicGestureUptime;
+
+    private static final Rect sTmpDisplayCutoutSafeExceptMaybeBarsRect = new Rect();
+    private static final Rect sTmpRect = new Rect();
+    private static final Rect sTmpDockedFrame = new Rect();
+    private static final Rect sTmpNavFrame = new Rect();
+    private static final Rect sTmpLastParentFrame = new Rect();
+
+    private WindowState mTopFullscreenOpaqueWindowState;
+    private WindowState mTopFullscreenOpaqueOrDimmingWindowState;
+    private WindowState mTopDockedOpaqueWindowState;
+    private WindowState mTopDockedOpaqueOrDimmingWindowState;
+    private boolean mTopIsFullscreen;
+    private boolean mForceStatusBar;
+    private boolean mForceStatusBarFromKeyguard;
+    private boolean mForceStatusBarTransparent;
+    private int mNavBarOpacityMode = NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED;
+    private boolean mForcingShowNavBar;
+    private int mForcingShowNavBarLayer;
+    private boolean mForceShowSystemBars;
+
+    private boolean mShowingDream;
+    private boolean mLastShowingDream;
+    private boolean mDreamingLockscreen;
+    private boolean mDreamingSleepTokenNeeded;
+    private boolean mWindowSleepTokenNeeded;
+    private boolean mLastWindowSleepTokenNeeded;
+    private boolean mAllowLockscreenWhenOn;
+
+    private InputConsumer mInputConsumer = null;
+
+    // -------- PolicyHandler --------
+    private static final int MSG_UPDATE_DREAMING_SLEEP_TOKEN = 1;
+    private static final int MSG_REQUEST_TRANSIENT_BARS = 2;
+    private static final int MSG_DISPOSE_INPUT_CONSUMER = 3;
+
+    private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
+    private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
+
+    private class PolicyHandler extends Handler {
+
+        PolicyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
+                    updateDreamingSleepToken(msg.arg1 != 0);
+                    break;
+                case MSG_REQUEST_TRANSIENT_BARS:
+                    WindowState targetBar = (msg.arg1 == MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS)
+                            ? mStatusBar : mNavigationBar;
+                    if (targetBar != null) {
+                        requestTransientBars(targetBar);
+                    }
+                    break;
+                case MSG_DISPOSE_INPUT_CONSUMER:
+                    disposeInputConsumer((InputConsumer) msg.obj);
+                    break;
+            }
+        }
+    }
+
+    DisplayPolicy(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
+        mContext = displayContent.isDefaultDisplay ? service.mContext
+                : service.mContext.createDisplayContext(displayContent.getDisplay());
+        mDisplayContent = displayContent;
         mLock = service.getWindowManagerLock();
-        mCarDockEnablesAccelerometer = service.mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_carDockEnablesAccelerometer);
-        mDeskDockEnablesAccelerometer = service.mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_deskDockEnablesAccelerometer);
+
+        final Resources r = mContext.getResources();
+        mCarDockEnablesAccelerometer = r.getBoolean(R.bool.config_carDockEnablesAccelerometer);
+        mDeskDockEnablesAccelerometer = r.getBoolean(R.bool.config_deskDockEnablesAccelerometer);
+        mTranslucentDecorEnabled = r.getBoolean(R.bool.config_enableTranslucentDecor);
+        updateConfigurationDependentBehaviors();
+
+        mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+        if (!displayContent.isDefaultDisplay) {
+            mAwake = true;
+            mScreenOnEarly = true;
+            mScreenOnFully = true;
+        }
+
+        final Looper looper = UiThread.getHandler().getLooper();
+        mHandler = new PolicyHandler(looper);
+        mSystemGestures = new SystemGesturesPointerEventListener(mContext, mHandler,
+                new SystemGesturesPointerEventListener.Callbacks() {
+                    @Override
+                    public void onSwipeFromTop() {
+                        if (mStatusBar != null) {
+                            requestTransientBars(mStatusBar);
+                        }
+                    }
+
+                    @Override
+                    public void onSwipeFromBottom() {
+                        if (mNavigationBar != null
+                                && mNavigationBarPosition == NAV_BAR_BOTTOM) {
+                            requestTransientBars(mNavigationBar);
+                        }
+                    }
+
+                    @Override
+                    public void onSwipeFromRight() {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_RIGHT) {
+                            requestTransientBars(mNavigationBar);
+                        }
+                    }
+
+                    @Override
+                    public void onSwipeFromLeft() {
+                        if (mNavigationBar != null && mNavigationBarPosition == NAV_BAR_LEFT) {
+                            requestTransientBars(mNavigationBar);
+                        }
+                    }
+
+                    @Override
+                    public void onFling(int duration) {
+                        if (mService.mPowerManagerInternal != null) {
+                            mService.mPowerManagerInternal.powerHint(
+                                    PowerHint.INTERACTION, duration);
+                        }
+                    }
+
+                    @Override
+                    public void onDebug() {
+                        // no-op
+                    }
+
+                    private WindowOrientationListener getOrientationListener() {
+                        final DisplayRotation rotation = mDisplayContent.getDisplayRotation();
+                        return rotation != null ? rotation.getOrientationListener() : null;
+                    }
+
+                    @Override
+                    public void onDown() {
+                        final WindowOrientationListener listener = getOrientationListener();
+                        if (listener != null) {
+                            listener.onTouchStart();
+                        }
+                    }
+
+                    @Override
+                    public void onUpOrCancel() {
+                        final WindowOrientationListener listener = getOrientationListener();
+                        if (listener != null) {
+                            listener.onTouchEnd();
+                        }
+                    }
+
+                    @Override
+                    public void onMouseHoverAtTop() {
+                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
+                        Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
+                        msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS;
+                        mHandler.sendMessageDelayed(msg, 500 /* delayMillis */);
+                    }
+
+                    @Override
+                    public void onMouseHoverAtBottom() {
+                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
+                        Message msg = mHandler.obtainMessage(MSG_REQUEST_TRANSIENT_BARS);
+                        msg.arg1 = MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION;
+                        mHandler.sendMessageDelayed(msg, 500 /* delayMillis */);
+                    }
+
+                    @Override
+                    public void onMouseLeaveFromEdge() {
+                        mHandler.removeMessages(MSG_REQUEST_TRANSIENT_BARS);
+                    }
+                });
+        displayContent.registerPointerEventListener(mSystemGestures);
+        displayContent.mAppTransition.registerListenerLocked(
+                mStatusBarController.getAppTransitionListener());
+        mImmersiveModeConfirmation = new ImmersiveModeConfirmation(mContext, looper,
+                mService.mVrModeEnabled);
+        mAcquireSleepTokenRunnable = () -> {
+            if (mWindowSleepToken != null) {
+                return;
+            }
+            final int displayId = displayContent.getDisplayId();
+            mWindowSleepToken = service.mAtmInternal.acquireSleepToken(
+                    "WindowSleepTokenOnDisplay" + displayId, displayId);
+        };
+        mReleaseSleepTokenRunnable = () -> {
+            if (mWindowSleepToken == null) {
+                return;
+            }
+            mWindowSleepToken.release();
+            mWindowSleepToken = null;
+        };
+
+        // TODO: Make it can take screenshot on external display
+        mScreenshotHelper = displayContent.isDefaultDisplay
+                ? new ScreenshotHelper(mContext) : null;
+    }
+
+    void systemReady() {
+        mSystemGestures.systemReady();
+    }
+
+    private int getDisplayId() {
+        return mDisplayContent.getDisplayId();
+    }
+
+    void onDisplayRemoved() {
+        mDisplayContent.unregisterPointerEventListener(mSystemGestures);
     }
 
     void configure(int width, int height, int shortSizeDp) {
         // Allow the navigation bar to move on non-square small devices (phones).
         mNavigationBarCanMove = width != height && shortSizeDp < 600;
 
-        mHasNavigationBar = mService.mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_showNavigationBar);
+        if (mDisplayContent.isDefaultDisplay) {
+            mHasNavigationBar = mContext.getResources().getBoolean(R.bool.config_showNavigationBar);
 
-        // Allow a system property to override this. Used by the emulator.
-        // See also hasNavigationBar().
-        String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
-        if ("1".equals(navBarOverride)) {
-            mHasNavigationBar = false;
-        } else if ("0".equals(navBarOverride)) {
-            mHasNavigationBar = true;
+            // Allow a system property to override this. Used by the emulator.
+            // See also hasNavigationBar().
+            String navBarOverride = SystemProperties.get("qemu.hw.mainkeys");
+            if ("1".equals(navBarOverride)) {
+                mHasNavigationBar = false;
+            } else if ("0".equals(navBarOverride)) {
+                mHasNavigationBar = true;
+            }
+        } else {
+            mHasNavigationBar = mDisplayContent.getDisplay().supportsSystemDecorations();
         }
     }
 
+    void updateConfigurationDependentBehaviors() {
+        mNavBarOpacityMode = mContext.getResources().getInteger(R.integer.config_navBarOpacityMode);
+    }
+
     public void setHdmiPlugged(boolean plugged) {
         setHdmiPlugged(plugged, false /* force */);
     }
@@ -101,7 +552,7 @@
             final Intent intent = new Intent(ACTION_HDMI_PLUGGED);
             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
             intent.putExtra(EXTRA_HDMI_PLUGGED_STATE, plugged);
-            mService.mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
+            mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
         }
     }
 
@@ -244,17 +695,2642 @@
         return true;
     }
 
+    /**
+     * Sanitize the layout parameters coming from a client.  Allows the policy
+     * to do things like ensure that windows of a specific type can't take
+     * input focus.
+     *
+     * @param attrs The window layout parameters to be modified.  These values
+     * are modified in-place.
+     */
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission) {
+
+        final boolean isScreenDecor = (attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
+        if (mScreenDecorWindows.contains(win)) {
+            if (!isScreenDecor) {
+                // No longer has the flag set, so remove from the set.
+                mScreenDecorWindows.remove(win);
+            }
+        } else if (isScreenDecor && hasStatusBarServicePermission) {
+            mScreenDecorWindows.add(win);
+        }
+
+        switch (attrs.type) {
+            case TYPE_SYSTEM_OVERLAY:
+            case TYPE_SECURE_SYSTEM_OVERLAY:
+                // These types of windows can't receive input events.
+                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+                attrs.flags &= ~WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
+                break;
+            case TYPE_DREAM:
+            case TYPE_WALLPAPER:
+                // Dreams and wallpapers don't have an app window token and can thus not be
+                // letterboxed. Hence always let them extend under the cutout.
+                attrs.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+                break;
+            case TYPE_STATUS_BAR:
+
+                // If the Keyguard is in a hidden state (occluded by another window), we force to
+                // remove the wallpaper and keyguard flag so that any change in-flight after setting
+                // the keyguard as occluded wouldn't set these flags again.
+                // See {@link #processKeyguardSetHiddenResultLw}.
+                if (mService.mPolicy.isKeyguardOccluded()) {
+                    attrs.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+                    attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+                }
+                break;
+
+            case TYPE_SCREENSHOT:
+                attrs.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+                break;
+
+            case TYPE_TOAST:
+                // While apps should use the dedicated toast APIs to add such windows
+                // it possible legacy apps to add the window directly. Therefore, we
+                // make windows added directly by the app behave as a toast as much
+                // as possible in terms of timeout and animation.
+                if (attrs.hideTimeoutMilliseconds < 0
+                        || attrs.hideTimeoutMilliseconds > TOAST_WINDOW_TIMEOUT) {
+                    attrs.hideTimeoutMilliseconds = TOAST_WINDOW_TIMEOUT;
+                }
+                attrs.windowAnimations = com.android.internal.R.style.Animation_Toast;
+                break;
+        }
+
+        if (attrs.type != TYPE_STATUS_BAR) {
+            // The status bar is the only window allowed to exhibit keyguard behavior.
+            attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
+        }
+    }
+
+    /**
+     * Preflight adding a window to the system.
+     *
+     * Currently enforces that three window types are singletons per display:
+     * <ul>
+     * <li>{@link WindowManager.LayoutParams#TYPE_STATUS_BAR}</li>
+     * <li>{@link WindowManager.LayoutParams#TYPE_NAVIGATION_BAR}</li>
+     * </ul>
+     *
+     * @param win The window to be added
+     * @param attrs Information about the window to be added
+     *
+     * @return If ok, WindowManagerImpl.ADD_OKAY.  If too many singletons,
+     * WindowManagerImpl.ADD_MULTIPLE_SINGLETON
+     */
+    public int prepareAddWindowLw(WindowState win, WindowManager.LayoutParams attrs) {
+
+        if ((attrs.privateFlags & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.STATUS_BAR_SERVICE,
+                    "DisplayPolicy");
+            mScreenDecorWindows.add(win);
+        }
+
+        switch (attrs.type) {
+            case TYPE_STATUS_BAR:
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE,
+                        "DisplayPolicy");
+                if (mStatusBar != null) {
+                    if (mStatusBar.isAlive()) {
+                        return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                    }
+                }
+                mStatusBar = win;
+                mStatusBarController.setWindow(win);
+                if (mDisplayContent.isDefaultDisplay) {
+                    mService.mPolicy.setKeyguardCandidateLw(win);
+                }
+                break;
+            case TYPE_NAVIGATION_BAR:
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE,
+                        "DisplayPolicy");
+                if (mNavigationBar != null) {
+                    if (mNavigationBar.isAlive()) {
+                        return WindowManagerGlobal.ADD_MULTIPLE_SINGLETON;
+                    }
+                }
+                mNavigationBar = win;
+                mNavigationBarController.setWindow(win);
+                mNavigationBarController.setOnBarVisibilityChangedListener(
+                        mNavBarVisibilityListener, true);
+                if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
+                break;
+            case TYPE_NAVIGATION_BAR_PANEL:
+            case TYPE_STATUS_BAR_PANEL:
+            case TYPE_STATUS_BAR_SUB_PANEL:
+            case TYPE_VOICE_INTERACTION_STARTING:
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.STATUS_BAR_SERVICE,
+                        "DisplayPolicy");
+                break;
+        }
+        return ADD_OKAY;
+    }
+
+    /**
+     * Called when a window is being removed from a window manager.  Must not
+     * throw an exception -- clean up as much as possible.
+     *
+     * @param win The window being removed.
+     */
+    public void removeWindowLw(WindowState win) {
+        if (mStatusBar == win) {
+            mStatusBar = null;
+            mStatusBarController.setWindow(null);
+            if (mDisplayContent.isDefaultDisplay) {
+                mService.mPolicy.setKeyguardCandidateLw(null);
+            }
+        } else if (mNavigationBar == win) {
+            mNavigationBar = null;
+            mNavigationBarController.setWindow(null);
+        }
+        if (mLastFocusedWindow == win) {
+            mLastFocusedWindow = null;
+        }
+        mScreenDecorWindows.remove(win);
+    }
+
+    /**
+     * Control the animation to run when a window's state changes.  Return a
+     * non-0 number to force the animation to a specific resource ID, or 0
+     * to use the default animation.
+     *
+     * @param win The window that is changing.
+     * @param transit What is happening to the window:
+     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_ENTER},
+     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_EXIT},
+     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_SHOW}, or
+     *                {@link com.android.server.policy.WindowManagerPolicy#TRANSIT_HIDE}.
+     *
+     * @return Resource ID of the actual animation to use, or 0 for none.
+     */
+    public int selectAnimationLw(WindowState win, int transit) {
+        if (DEBUG_ANIM) Slog.i(TAG, "selectAnimation in " + win
+                + ": transit=" + transit);
+        if (win == mStatusBar) {
+            final boolean isKeyguard = (win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
+            final boolean expanded = win.getAttrs().height == MATCH_PARENT
+                    && win.getAttrs().width == MATCH_PARENT;
+            if (isKeyguard || expanded) {
+                return -1;
+            }
+            if (transit == TRANSIT_EXIT
+                    || transit == TRANSIT_HIDE) {
+                return R.anim.dock_top_exit;
+            } else if (transit == TRANSIT_ENTER
+                    || transit == TRANSIT_SHOW) {
+                return R.anim.dock_top_enter;
+            }
+        } else if (win == mNavigationBar) {
+            if (win.getAttrs().windowAnimations != 0) {
+                return 0;
+            }
+            // This can be on either the bottom or the right or the left.
+            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
+                if (transit == TRANSIT_EXIT
+                        || transit == TRANSIT_HIDE) {
+                    if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
+                        return R.anim.dock_bottom_exit_keyguard;
+                    } else {
+                        return R.anim.dock_bottom_exit;
+                    }
+                } else if (transit == TRANSIT_ENTER
+                        || transit == TRANSIT_SHOW) {
+                    return R.anim.dock_bottom_enter;
+                }
+            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+                if (transit == TRANSIT_EXIT
+                        || transit == TRANSIT_HIDE) {
+                    return R.anim.dock_right_exit;
+                } else if (transit == TRANSIT_ENTER
+                        || transit == TRANSIT_SHOW) {
+                    return R.anim.dock_right_enter;
+                }
+            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                if (transit == TRANSIT_EXIT
+                        || transit == TRANSIT_HIDE) {
+                    return R.anim.dock_left_exit;
+                } else if (transit == TRANSIT_ENTER
+                        || transit == TRANSIT_SHOW) {
+                    return R.anim.dock_left_enter;
+                }
+            }
+        } else if (win.getAttrs().type == TYPE_DOCK_DIVIDER) {
+            return selectDockedDividerAnimationLw(win, transit);
+        }
+
+        if (transit == TRANSIT_PREVIEW_DONE) {
+            if (win.hasAppShownWindows()) {
+                if (DEBUG_ANIM) Slog.i(TAG, "**** STARTING EXIT");
+                return R.anim.app_starting_exit;
+            }
+        } else if (win.getAttrs().type == TYPE_DREAM && mDreamingLockscreen
+                && transit == TRANSIT_ENTER) {
+            // Special case: we are animating in a dream, while the keyguard
+            // is shown.  We don't want an animation on the dream, because
+            // we need it shown immediately with the keyguard animating away
+            // to reveal it.
+            return -1;
+        }
+
+        return 0;
+    }
+
+    private int selectDockedDividerAnimationLw(WindowState win, int transit) {
+        int insets = mDisplayContent.getDockedDividerController().getContentInsets();
+
+        // If the divider is behind the navigation bar, don't animate.
+        final Rect frame = win.getFrameLw();
+        final boolean behindNavBar = mNavigationBar != null
+                && ((mNavigationBarPosition == NAV_BAR_BOTTOM
+                && frame.top + insets >= mNavigationBar.getFrameLw().top)
+                || (mNavigationBarPosition == NAV_BAR_RIGHT
+                && frame.left + insets >= mNavigationBar.getFrameLw().left)
+                || (mNavigationBarPosition == NAV_BAR_LEFT
+                && frame.right - insets <= mNavigationBar.getFrameLw().right));
+        final boolean landscape = frame.height() > frame.width();
+        final boolean offscreenLandscape = landscape && (frame.right - insets <= 0
+                || frame.left + insets >= win.getDisplayFrameLw().right);
+        final boolean offscreenPortrait = !landscape && (frame.top - insets <= 0
+                || frame.bottom + insets >= win.getDisplayFrameLw().bottom);
+        final boolean offscreen = offscreenLandscape || offscreenPortrait;
+        if (behindNavBar || offscreen) {
+            return 0;
+        }
+        if (transit == TRANSIT_ENTER || transit == TRANSIT_SHOW) {
+            return R.anim.fade_in;
+        } else if (transit == TRANSIT_EXIT) {
+            return R.anim.fade_out;
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+     * Determine the animation to run for a rotation transition based on the
+     * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
+     * and whether it is currently fullscreen and frontmost.
+     *
+     * @param anim The exiting animation resource id is stored in anim[0], the
+     * entering animation resource id is stored in anim[1].
+     */
+    public void selectRotationAnimationLw(int anim[]) {
+        // If the screen is off or non-interactive, force a jumpcut.
+        final boolean forceJumpcut = !mScreenOnFully || !mService.mPolicy.okToAnimate();
+        if (DEBUG_ANIM) Slog.i(TAG, "selectRotationAnimation mTopFullscreen="
+                + mTopFullscreenOpaqueWindowState + " rotationAnimation="
+                + (mTopFullscreenOpaqueWindowState == null
+                ? "0" : mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation)
+                + " forceJumpcut=" + forceJumpcut);
+        if (forceJumpcut) {
+            anim[0] = R.anim.rotation_animation_jump_exit;
+            anim[1] = R.anim.rotation_animation_enter;
+            return;
+        }
+        if (mTopFullscreenOpaqueWindowState != null) {
+            int animationHint = mTopFullscreenOpaqueWindowState.getRotationAnimationHint();
+            if (animationHint < 0 && mTopIsFullscreen) {
+                animationHint = mTopFullscreenOpaqueWindowState.getAttrs().rotationAnimation;
+            }
+            switch (animationHint) {
+                case ROTATION_ANIMATION_CROSSFADE:
+                case ROTATION_ANIMATION_SEAMLESS: // Crossfade is fallback for seamless.
+                    anim[0] = R.anim.rotation_animation_xfade_exit;
+                    anim[1] = R.anim.rotation_animation_enter;
+                    break;
+                case ROTATION_ANIMATION_JUMPCUT:
+                    anim[0] = R.anim.rotation_animation_jump_exit;
+                    anim[1] = R.anim.rotation_animation_enter;
+                    break;
+                case ROTATION_ANIMATION_ROTATE:
+                default:
+                    anim[0] = anim[1] = 0;
+                    break;
+            }
+        } else {
+            anim[0] = anim[1] = 0;
+        }
+    }
+
+    /**
+     * Validate whether the current top fullscreen has specified the same
+     * {@link WindowManager.LayoutParams#rotationAnimation} value as that
+     * being passed in from the previous top fullscreen window.
+     *
+     * @param exitAnimId exiting resource id from the previous window.
+     * @param enterAnimId entering resource id from the previous window.
+     * @param forceDefault For rotation animations only, if true ignore the
+     * animation values and just return false.
+     * @return true if the previous values are still valid, false if they
+     * should be replaced with the default.
+     */
+    public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+            boolean forceDefault) {
+        switch (exitAnimId) {
+            case R.anim.rotation_animation_xfade_exit:
+            case R.anim.rotation_animation_jump_exit:
+                // These are the only cases that matter.
+                if (forceDefault) {
+                    return false;
+                }
+                int anim[] = new int[2];
+                selectRotationAnimationLw(anim);
+                return (exitAnimId == anim[0] && enterAnimId == anim[1]);
+            default:
+                return true;
+        }
+    }
+
+    /**
+     * Called when a new system UI visibility is being reported, allowing
+     * the policy to adjust what is actually reported.
+     * @param visibility The raw visibility reported by the status bar.
+     * @return The new desired visibility.
+     */
+    public int adjustSystemUiVisibilityLw(int visibility) {
+        mStatusBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
+        mNavigationBarController.adjustSystemUiVisibilityLw(mLastSystemUiFlags, visibility);
+
+        // Reset any bits in mForceClearingStatusBarVisibility that
+        // are now clear.
+        mResettingSystemUiFlags &= visibility;
+        // Clear any bits in the new visibility that are currently being
+        // force cleared, before reporting it.
+        return visibility & ~mResettingSystemUiFlags
+                & ~mForceClearedSystemUiFlags;
+    }
+
+    /**
+     * @return true if the navigation bar is forced to stay visible
+     */
+    public boolean isNavBarForcedShownLw(WindowState windowState) {
+        return mForceShowSystemBars;
+    }
+
+    // TODO: Should probably be moved into DisplayFrames.
+    /**
+     * Return the layout hints for a newly added window. These values are computed on the
+     * most recent layout, so they are not guaranteed to be correct.
+     *
+     * @param attrs The LayoutParams of the window.
+     * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
+     *                   associated with the window.
+     * @param displayFrames display frames.
+     * @param floatingStack Whether the window's stack is floating.
+     * @param outFrame The frame of the window.
+     * @param outContentInsets The areas covered by system windows, expressed as positive insets.
+     * @param outStableInsets The areas covered by stable system windows irrespective of their
+     *                        current visibility. Expressed as positive insets.
+     * @param outOutsets The areas that are not real display, but we would like to treat as such.
+     * @param outDisplayCutout The area that has been cut away from the display.
+     * @return Whether to always consume the navigation bar.
+     *         See {@link #isNavBarForcedShownLw(WindowState)}.
+     */
+    public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds,
+            DisplayFrames displayFrames, boolean floatingStack, Rect outFrame,
+            Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) {
+        final int fl = PolicyControl.getWindowFlags(null, attrs);
+        final int pfl = attrs.privateFlags;
+        final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);
+        final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs);
+        final int displayRotation = displayFrames.mRotation;
+
+        final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
+        if (useOutsets) {
+            int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
+            if (outset > 0) {
+                if (displayRotation == Surface.ROTATION_0) {
+                    outOutsets.bottom += outset;
+                } else if (displayRotation == Surface.ROTATION_90) {
+                    outOutsets.right += outset;
+                } else if (displayRotation == Surface.ROTATION_180) {
+                    outOutsets.top += outset;
+                } else if (displayRotation == Surface.ROTATION_270) {
+                    outOutsets.left += outset;
+                }
+            }
+        }
+
+        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) != 0;
+        final boolean layoutInScreenAndInsetDecor = layoutInScreen
+                && (fl & FLAG_LAYOUT_INSET_DECOR) != 0;
+        final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;
+
+        if (layoutInScreenAndInsetDecor && !screenDecor) {
+            if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
+                outFrame.set(displayFrames.mUnrestricted);
+            } else {
+                outFrame.set(displayFrames.mRestricted);
+            }
+
+            final Rect sf;
+            if (floatingStack) {
+                sf = null;
+            } else {
+                sf = displayFrames.mStable;
+            }
+
+            final Rect cf;
+            if (floatingStack) {
+                cf = null;
+            } else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
+                if ((fl & FLAG_FULLSCREEN) != 0) {
+                    cf = displayFrames.mStableFullscreen;
+                } else {
+                    cf = displayFrames.mStable;
+                }
+            } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
+                cf = displayFrames.mOverscan;
+            } else {
+                cf = displayFrames.mCurrent;
+            }
+
+            if (taskBounds != null) {
+                outFrame.intersect(taskBounds);
+            }
+            InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets);
+            InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets);
+            outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)
+                    .getDisplayCutout());
+            return mForceShowSystemBars;
+        } else {
+            if (layoutInScreen) {
+                outFrame.set(displayFrames.mUnrestricted);
+            } else {
+                outFrame.set(displayFrames.mStable);
+            }
+            if (taskBounds != null) {
+                outFrame.intersect(taskBounds);
+            }
+
+            outContentInsets.setEmpty();
+            outStableInsets.setEmpty();
+            outDisplayCutout.set(DisplayCutout.NO_CUTOUT);
+            return mForceShowSystemBars;
+        }
+    }
+
+    private static int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
+        int impliedFlags = 0;
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+            impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        }
+        final boolean forceWindowDrawsStatusBarBackground =
+                (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                || forceWindowDrawsStatusBarBackground
+                && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
+            impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+        }
+        return impliedFlags;
+    }
+
+    private static boolean shouldUseOutsets(WindowManager.LayoutParams attrs, int fl) {
+        return attrs.type == TYPE_WALLPAPER || (fl & (WindowManager.LayoutParams.FLAG_FULLSCREEN
+                | WindowManager.LayoutParams.FLAG_LAYOUT_IN_OVERSCAN)) != 0;
+    }
+
+    private final Runnable mClearHideNavigationFlag = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                mForceClearedSystemUiFlags &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+                mDisplayContent.reevaluateStatusBarVisibility();
+            }
+        }
+    };
+
+    /**
+     * Input handler used while nav bar is hidden.  Captures any touch on the screen,
+     * to determine when the nav bar should be shown and prevent applications from
+     * receiving those touches.
+     */
+    private final class HideNavInputEventReceiver extends InputEventReceiver {
+        HideNavInputEventReceiver(InputChannel inputChannel, Looper looper) {
+            super(inputChannel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            try {
+                if (event instanceof MotionEvent
+                        && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) {
+                    final MotionEvent motionEvent = (MotionEvent) event;
+                    if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
+                        // When the user taps down, we re-show the nav bar.
+                        boolean changed = false;
+                        synchronized (mLock) {
+                            if (mInputConsumer == null) {
+                                return;
+                            }
+                            // Any user activity always causes us to show the
+                            // navigation controls, if they had been hidden.
+                            // We also clear the low profile and only content
+                            // flags so that tapping on the screen will atomically
+                            // restore all currently hidden screen decorations.
+                            int newVal = mResettingSystemUiFlags
+                                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                                    | View.SYSTEM_UI_FLAG_LOW_PROFILE
+                                    | View.SYSTEM_UI_FLAG_FULLSCREEN;
+                            if (mResettingSystemUiFlags != newVal) {
+                                mResettingSystemUiFlags = newVal;
+                                changed = true;
+                            }
+                            // We don't allow the system's nav bar to be hidden
+                            // again for 1 second, to prevent applications from
+                            // spamming us and keeping it from being shown.
+                            newVal = mForceClearedSystemUiFlags
+                                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+                            if (mForceClearedSystemUiFlags != newVal) {
+                                mForceClearedSystemUiFlags = newVal;
+                                changed = true;
+                                mHandler.postDelayed(mClearHideNavigationFlag, 1000);
+                            }
+                            if (changed) {
+                                mDisplayContent.reevaluateStatusBarVisibility();
+                            }
+                        }
+                    }
+                }
+            } finally {
+                finishInputEvent(event, false /* handled */);
+            }
+        }
+    }
+
+    /**
+     * Called when layout of the windows is about to start.
+     *
+     * @param displayFrames frames of the display we are doing layout on.
+     * @param uiMode The current uiMode in configuration.
+     */
+    public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
+        displayFrames.onBeginLayout();
+        mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
+        mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
+
+        // For purposes of putting out fake window up to steal focus, we will
+        // drive nav being hidden only by whether it is requested.
+        final int sysui = mLastSystemUiFlags;
+        boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
+        boolean navTranslucent = (sysui
+                & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0;
+        boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
+        boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+        boolean navAllowedHidden = immersive || immersiveSticky;
+        navTranslucent &= !immersiveSticky;  // transient trumps translucent
+        boolean isKeyguardShowing = isStatusBarKeyguard()
+                && !mService.mPolicy.isKeyguardOccluded();
+        if (!isKeyguardShowing) {
+            navTranslucent &= areTranslucentBarsAllowed();
+        }
+        boolean statusBarForcesShowingNavigation = !isKeyguardShowing && mStatusBar != null
+                && (mStatusBar.getAttrs().privateFlags
+                & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
+
+        // When the navigation bar isn't visible, we put up a fake input window to catch all
+        // touch events. This way we can detect when the user presses anywhere to bring back the
+        // nav bar and ensure the application doesn't see the event.
+        if (navVisible || navAllowedHidden) {
+            if (mInputConsumer != null) {
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer));
+                mInputConsumer = null;
+            }
+        } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) {
+            mInputConsumer = mService.createInputConsumer(mHandler.getLooper(),
+                    INPUT_CONSUMER_NAVIGATION,
+                    HideNavInputEventReceiver::new,
+                    displayFrames.mDisplayId);
+            // As long as mInputConsumer is active, hover events are not dispatched to the app
+            // and the pointer icon is likely to become stale. Hide it to avoid confusion.
+            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
+        }
+
+        // For purposes of positioning and showing the nav bar, if we have decided that it can't
+        // be hidden (because of the screen aspect ratio), then take that into account.
+        navVisible |= !canHideNavigationBar();
+
+        boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, navVisible,
+                navTranslucent, navAllowedHidden, statusBarForcesShowingNavigation);
+        if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
+        updateSysUiVisibility |= layoutStatusBar(displayFrames, sysui, isKeyguardShowing);
+        if (updateSysUiVisibility) {
+            updateSystemUiVisibilityLw();
+        }
+        layoutScreenDecorWindows(displayFrames);
+
+        if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
+            // Make sure that the zone we're avoiding for the cutout is at least as tall as the
+            // status bar; otherwise fullscreen apps will end up cutting halfway into the status
+            // bar.
+            displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
+                    displayFrames.mStable.top);
+        }
+    }
+
+    private void layoutScreenDecorWindows(DisplayFrames displayFrames) {
+        if (mScreenDecorWindows.isEmpty()) {
+            return;
+        }
+
+        sTmpRect.setEmpty();
+        sTmpDockedFrame.set(displayFrames.mDock);
+
+        final int displayId = displayFrames.mDisplayId;
+        final Rect dockFrame = displayFrames.mDock;
+        final int displayHeight = displayFrames.mDisplayHeight;
+        final int displayWidth = displayFrames.mDisplayWidth;
+
+        for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) {
+            final WindowState w = mScreenDecorWindows.valueAt(i);
+            if (w.getDisplayId() != displayId || !w.isVisibleLw()) {
+                // Skip if not on the same display or not visible.
+                continue;
+            }
+
+            w.getWindowFrames().setFrames(sTmpDockedFrame /* parentFrame */,
+                    sTmpDockedFrame /* displayFrame */, sTmpDockedFrame /* overscanFrame */,
+                    sTmpDockedFrame /* contentFrame */, sTmpDockedFrame /* visibleFrame */,
+                    sTmpRect /* decorFrame */, sTmpDockedFrame /* stableFrame */,
+                    sTmpDockedFrame /* outsetFrame */);
+            w.getWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout);
+            w.computeFrameLw();
+            final Rect frame = w.getFrameLw();
+
+            if (frame.left <= 0 && frame.top <= 0) {
+                // Docked at left or top.
+                if (frame.bottom >= displayHeight) {
+                    // Docked left.
+                    dockFrame.left = Math.max(frame.right, dockFrame.left);
+                } else if (frame.right >= displayWidth) {
+                    // Docked top.
+                    dockFrame.top = Math.max(frame.bottom, dockFrame.top);
+                } else {
+                    Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                            + " not docked on left or top of display. frame=" + frame
+                            + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+                }
+            } else if (frame.right >= displayWidth && frame.bottom >= displayHeight) {
+                // Docked at right or bottom.
+                if (frame.top <= 0) {
+                    // Docked right.
+                    dockFrame.right = Math.min(frame.left, dockFrame.right);
+                } else if (frame.left <= 0) {
+                    // Docked bottom.
+                    dockFrame.bottom = Math.min(frame.top, dockFrame.bottom);
+                } else {
+                    Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                            + " not docked on right or bottom" + " of display. frame=" + frame
+                            + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+                }
+            } else {
+                // Screen decor windows are required to be docked on one of the sides of the screen.
+                Slog.w(TAG, "layoutScreenDecorWindows: Ignoring decor win=" + w
+                        + " not docked on one of the sides of the display. frame=" + frame
+                        + " displayWidth=" + displayWidth + " displayHeight=" + displayHeight);
+            }
+        }
+
+        displayFrames.mRestricted.set(dockFrame);
+        displayFrames.mCurrent.set(dockFrame);
+        displayFrames.mVoiceContent.set(dockFrame);
+        displayFrames.mSystem.set(dockFrame);
+        displayFrames.mContent.set(dockFrame);
+        displayFrames.mRestrictedOverscan.set(dockFrame);
+    }
+
+    private boolean layoutStatusBar(DisplayFrames displayFrames, int sysui,
+            boolean isKeyguardShowing) {
+        // decide where the status bar goes ahead of time
+        if (mStatusBar == null) {
+            return false;
+        }
+        // apply any navigation bar insets
+        sTmpRect.setEmpty();
+        mStatusBar.getWindowFrames().setFrames(displayFrames.mUnrestricted /* parentFrame */,
+                displayFrames.mUnrestricted /* displayFrame */,
+                displayFrames.mStable /* overscanFrame */, displayFrames.mStable /* contentFrame */,
+                displayFrames.mStable /* visibleFrame */, sTmpRect /* decorFrame */,
+                displayFrames.mStable /* stableFrame */, displayFrames.mStable /* outsetFrame */);
+        mStatusBar.getWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout);
+
+        // Let the status bar determine its size.
+        mStatusBar.computeFrameLw();
+
+        // For layout, the status bar is always at the top with our fixed height.
+        displayFrames.mStable.top = displayFrames.mUnrestricted.top
+                + mStatusBarHeightForRotation[displayFrames.mRotation];
+        // Make sure the status bar covers the entire cutout height
+        displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
+                displayFrames.mDisplayCutoutSafe.top);
+
+        // Tell the bar controller where the collapsed status bar content is
+        sTmpRect.set(mStatusBar.getContentFrameLw());
+        sTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
+        sTmpRect.top = mStatusBar.getContentFrameLw().top;  // Ignore top display cutout inset
+        sTmpRect.bottom = displayFrames.mStable.top;  // Use collapsed status bar size
+        mStatusBarController.setContentFrame(sTmpRect);
+
+        boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
+        boolean statusBarTranslucent = (sysui
+                & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
+        if (!isKeyguardShowing) {
+            statusBarTranslucent &= areTranslucentBarsAllowed();
+        }
+
+        // If the status bar is hidden, we don't want to cause windows behind it to scroll.
+        if (mStatusBar.isVisibleLw() && !statusBarTransient) {
+            // Status bar may go away, so the screen area it occupies is available to apps but just
+            // covering them when the status bar is visible.
+            final Rect dockFrame = displayFrames.mDock;
+            dockFrame.top = displayFrames.mStable.top;
+            displayFrames.mContent.set(dockFrame);
+            displayFrames.mVoiceContent.set(dockFrame);
+            displayFrames.mCurrent.set(dockFrame);
+
+            if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
+                    "dock=%s content=%s cur=%s", dockFrame.toString(),
+                    displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
+
+            if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
+                    && !mStatusBarController.wasRecentlyTranslucent()) {
+                // If the opaque status bar is currently requested to be visible, and not in the
+                // process of animating on or off, then we can tell the app that it is covered by
+                // it.
+                displayFrames.mSystem.top = displayFrames.mStable.top;
+            }
+        }
+        return mStatusBarController.checkHiddenLw();
+    }
+
+    private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, boolean navVisible,
+            boolean navTranslucent, boolean navAllowedHidden,
+            boolean statusBarForcesShowingNavigation) {
+        if (mNavigationBar == null) {
+            return false;
+        }
+
+        final Rect navigationFrame = sTmpNavFrame;
+        boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
+        // Force the navigation bar to its appropriate place and size. We need to do this directly,
+        // instead of relying on it to bubble up from the nav bar, because this needs to change
+        // atomically with screen rotations.
+        final int rotation = displayFrames.mRotation;
+        final int displayHeight = displayFrames.mDisplayHeight;
+        final int displayWidth = displayFrames.mDisplayWidth;
+        final Rect dockFrame = displayFrames.mDock;
+        mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
+
+        final Rect cutoutSafeUnrestricted = sTmpRect;
+        cutoutSafeUnrestricted.set(displayFrames.mUnrestricted);
+        cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+
+        if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
+            // It's a system nav bar or a portrait screen; nav bar goes on bottom.
+            final int top = cutoutSafeUnrestricted.bottom
+                    - getNavigationBarHeight(rotation, uiMode);
+            // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+            final int topNavBar = cutoutSafeUnrestricted.bottom
+                    - mExperiments.getNavigationBarFrameHeight();
+            navigationFrame.set(0, topNavBar, displayWidth, displayFrames.mUnrestricted.bottom);
+            // EXPERIMENT END
+            displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.bottom = displayFrames.mRestricted.bottom =
+                        displayFrames.mRestrictedOverscan.bottom = top;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the opaque nav bar is currently requested to be visible and not in the process
+                // of animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.bottom = top;
+            }
+        } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+            // Landscape screen; nav bar goes to the right.
+            final int left = cutoutSafeUnrestricted.right
+                    - getNavigationBarWidth(rotation, uiMode);
+            // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+            final int leftNavBar = cutoutSafeUnrestricted.right
+                    - mExperiments.getNavigationBarFrameWidth();
+            navigationFrame.set(leftNavBar, 0, displayFrames.mUnrestricted.right, displayHeight);
+            // EXPERIMENT END
+            displayFrames.mStable.right = displayFrames.mStableFullscreen.right = left;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.right = displayFrames.mRestricted.right =
+                        displayFrames.mRestrictedOverscan.right = left;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the nav bar is currently requested to be visible, and not in the process of
+                // animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.right = left;
+            }
+        } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+            // Seascape screen; nav bar goes to the left.
+            final int right = cutoutSafeUnrestricted.left
+                    + getNavigationBarWidth(rotation, uiMode);
+            // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+            final int rightNavBar = cutoutSafeUnrestricted.left
+                    + mExperiments.getNavigationBarFrameWidth();
+            navigationFrame.set(displayFrames.mUnrestricted.left, 0, rightNavBar, displayHeight);
+            // EXPERIMENT END
+            displayFrames.mStable.left = displayFrames.mStableFullscreen.left = right;
+            if (transientNavBarShowing) {
+                mNavigationBarController.setBarShowingLw(true);
+            } else if (navVisible) {
+                mNavigationBarController.setBarShowingLw(true);
+                dockFrame.left = displayFrames.mRestricted.left =
+                        displayFrames.mRestrictedOverscan.left = right;
+            } else {
+                // We currently want to hide the navigation UI - unless we expanded the status bar.
+                mNavigationBarController.setBarShowingLw(statusBarForcesShowingNavigation);
+            }
+            if (navVisible && !navTranslucent && !navAllowedHidden
+                    && !mNavigationBar.isAnimatingLw()
+                    && !mNavigationBarController.wasRecentlyTranslucent()) {
+                // If the nav bar is currently requested to be visible, and not in the process of
+                // animating on or off, then we can tell the app that it is covered by it.
+                displayFrames.mSystem.left = right;
+            }
+        }
+
+        // Make sure the content and current rectangles are updated to account for the restrictions
+        // from the navigation bar.
+        displayFrames.mCurrent.set(dockFrame);
+        displayFrames.mVoiceContent.set(dockFrame);
+        displayFrames.mContent.set(dockFrame);
+        // And compute the final frame.
+        sTmpRect.setEmpty();
+        mNavigationBar.getWindowFrames().setFrames(navigationFrame /* parentFrame */,
+                navigationFrame /* displayFrame */, navigationFrame /* overscanFrame */,
+                displayFrames.mDisplayCutoutSafe /* contentFrame */,
+                navigationFrame /* visibleFrame */, sTmpRect /* decorFrame */,
+                navigationFrame /* stableFrame */,
+                displayFrames.mDisplayCutoutSafe /* outsetFrame */);
+        mNavigationBar.getWindowFrames().setDisplayCutout(displayFrames.mDisplayCutout);
+        mNavigationBar.computeFrameLw();
+        mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw());
+
+        if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + navigationFrame);
+        return mNavigationBarController.checkHiddenLw();
+    }
+
+    private void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
+            boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf,
+            DisplayFrames displayFrames) {
+        if (!win.isInputMethodTarget() && attached.isInputMethodTarget()) {
+            // Here's a special case: if the child window is not the 'dock window'
+            // or input method target, and the window it is attached to is below
+            // the dock window, then the frames we computed for the window it is
+            // attached to can not be used because the dock is effectively part
+            // of the underlying window and the attached window is floating on top
+            // of the whole thing. So, we ignore the attached window and explicitly
+            // compute the frames that would be appropriate without the dock.
+            vf.set(displayFrames.mDock);
+            cf.set(displayFrames.mDock);
+            of.set(displayFrames.mDock);
+            df.set(displayFrames.mDock);
+        } else {
+            // The effective display frame of the attached window depends on whether it is taking
+            // care of insetting its content. If not, we need to use the parent's content frame so
+            // that the entire window is positioned within that content. Otherwise we can use the
+            // overscan frame and let the attached window take care of positioning its content
+            // appropriately.
+            if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+                // Set the content frame of the attached window to the parent's decor frame
+                // (same as content frame when IME isn't present) if specifically requested by
+                // setting {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR} flag.
+                // Otherwise, use the overscan frame.
+                cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
+                        ? attached.getContentFrameLw() : attached.getOverscanFrameLw());
+            } else {
+                // If the window is resizing, then we want to base the content frame on our attached
+                // content frame to resize...however, things can be tricky if the attached window is
+                // NOT in resize mode, in which case its content frame will be larger.
+                // Ungh. So to deal with that, make sure the content frame we end up using is not
+                // covering the IM dock.
+                cf.set(attached.getContentFrameLw());
+                if (attached.isVoiceInteraction()) {
+                    cf.intersectUnchecked(displayFrames.mVoiceContent);
+                } else if (win.isInputMethodTarget() || attached.isInputMethodTarget()) {
+                    cf.intersectUnchecked(displayFrames.mContent);
+                }
+            }
+            df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
+            of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
+            vf.set(attached.getVisibleFrameLw());
+        }
+        // The LAYOUT_IN_SCREEN flag is used to determine whether the attached window should be
+        // positioned relative to its parent or the entire screen.
+        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
+    }
+
+    private void applyStableConstraints(int sysui, int fl, Rect r, DisplayFrames displayFrames) {
+        if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) == 0) {
+            return;
+        }
+        // If app is requesting a stable layout, don't let the content insets go below the stable
+        // values.
+        if ((fl & FLAG_FULLSCREEN) != 0) {
+            r.intersectUnchecked(displayFrames.mStableFullscreen);
+        } else {
+            r.intersectUnchecked(displayFrames.mStable);
+        }
+    }
+
+    private boolean canReceiveInput(WindowState win) {
+        boolean notFocusable =
+                (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) != 0;
+        boolean altFocusableIm =
+                (win.getAttrs().flags & WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM) != 0;
+        boolean notFocusableForIm = notFocusable ^ altFocusableIm;
+        return !notFocusableForIm;
+    }
+
+    /**
+     * Called for each window attached to the window manager as layout is proceeding. The
+     * implementation of this function must take care of setting the window's frame, either here or
+     * in finishLayout().
+     *
+     * @param win The window being positioned.
+     * @param attached For sub-windows, the window it is attached to; this
+     *                 window will already have had layoutWindow() called on it
+     *                 so you can use its Rect.  Otherwise null.
+     * @param displayFrames The display frames.
+     */
+    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
+        // We've already done the navigation bar, status bar, and all screen decor windows. If the
+        // status bar can receive input, we need to layout it again to accommodate for the IME
+        // window.
+        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
+                || mScreenDecorWindows.contains(win)) {
+            return;
+        }
+        final WindowManager.LayoutParams attrs = win.getAttrs();
+        final boolean isDefaultDisplay = win.isDefaultDisplay();
+
+        final int type = attrs.type;
+        final int fl = PolicyControl.getWindowFlags(win, attrs);
+        final int pfl = attrs.privateFlags;
+        final int sim = attrs.softInputMode;
+        final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(null, attrs);
+        final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
+
+        final WindowFrames windowFrames = win.getWindowFrames();
+
+        windowFrames.setHasOutsets(false);
+        sTmpLastParentFrame.set(windowFrames.mParentFrame);
+        final Rect pf = windowFrames.mParentFrame;
+        final Rect df = windowFrames.mDisplayFrame;
+        final Rect of = windowFrames.mOverscanFrame;
+        final Rect cf = windowFrames.mContentFrame;
+        final Rect vf = windowFrames.mVisibleFrame;
+        final Rect dcf = windowFrames.mDecorFrame;
+        final Rect sf = windowFrames.mStableFrame;
+        dcf.setEmpty();
+        windowFrames.setParentFrameWasClippedByDisplayCutout(false);
+        windowFrames.setDisplayCutout(displayFrames.mDisplayCutout);
+
+        final boolean hasNavBar = hasNavigationBar() && mNavigationBar != null
+                && mNavigationBar.isVisibleLw();
+
+        final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
+
+        final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
+                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+
+        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
+        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
+
+        sf.set(displayFrames.mStable);
+
+        if (type == TYPE_INPUT_METHOD) {
+            vf.set(displayFrames.mDock);
+            cf.set(displayFrames.mDock);
+            of.set(displayFrames.mDock);
+            df.set(displayFrames.mDock);
+            windowFrames.mParentFrame.set(displayFrames.mDock);
+            // IM dock windows layout below the nav bar...
+            pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
+            // ...with content insets above the nav bar
+            cf.bottom = vf.bottom = displayFrames.mStable.bottom;
+            if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
+                // The status bar forces the navigation bar while it's visible. Make sure the IME
+                // avoids the navigation bar in that case.
+                if (mNavigationBarPosition == NAV_BAR_RIGHT) {
+                    pf.right = df.right = of.right = cf.right = vf.right =
+                            displayFrames.mStable.right;
+                } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
+                    pf.left = df.left = of.left = cf.left = vf.left = displayFrames.mStable.left;
+                }
+            }
+
+            // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+            // Offset the ime to avoid overlapping with the nav bar
+            mExperiments.offsetWindowFramesForNavBar(mNavigationBarPosition, win);
+            // EXPERIMENT END
+
+            // IM dock windows always go to the bottom of the screen.
+            attrs.gravity = Gravity.BOTTOM;
+        } else if (type == TYPE_VOICE_INTERACTION) {
+            of.set(displayFrames.mUnrestricted);
+            df.set(displayFrames.mUnrestricted);
+            pf.set(displayFrames.mUnrestricted);
+            if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+                cf.set(displayFrames.mDock);
+            } else {
+                cf.set(displayFrames.mContent);
+            }
+            if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                vf.set(displayFrames.mCurrent);
+            } else {
+                vf.set(cf);
+            }
+        } else if (type == TYPE_WALLPAPER) {
+            layoutWallpaper(displayFrames, pf, df, of, cf);
+        } else if (win == mStatusBar) {
+            of.set(displayFrames.mUnrestricted);
+            df.set(displayFrames.mUnrestricted);
+            pf.set(displayFrames.mUnrestricted);
+            cf.set(displayFrames.mStable);
+            vf.set(displayFrames.mStable);
+
+            if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
+                cf.bottom = displayFrames.mContent.bottom;
+            } else {
+                cf.bottom = displayFrames.mDock.bottom;
+                vf.bottom = displayFrames.mContent.bottom;
+            }
+        } else {
+            dcf.set(displayFrames.mSystem);
+            final boolean inheritTranslucentDecor =
+                    (attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
+            final boolean isAppWindow =
+                    type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
+            final boolean topAtRest =
+                    win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
+            if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
+                if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
+                        && (fl & FLAG_FULLSCREEN) == 0
+                        && (fl & FLAG_TRANSLUCENT_STATUS) == 0
+                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
+                        && (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
+                    // Ensure policy decor includes status bar
+                    dcf.top = displayFrames.mStable.top;
+                }
+                if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
+                        && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
+                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
+                    // Ensure policy decor includes navigation bar
+                    dcf.bottom = displayFrames.mStable.bottom;
+                    dcf.right = displayFrames.mStable.right;
+                }
+            }
+
+            if (layoutInScreen && layoutInsetDecor) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): IN_SCREEN, INSET_DECOR");
+                // This is the case for a normal activity window: we want it to cover all of the
+                // screen space, and it can take care of moving its contents to account for screen
+                // decorations that intrude into that space.
+                if (attached != null) {
+                    // If this window is attached to another, our display
+                    // frame is the same as the one we are attached to.
+                    setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
+                            displayFrames);
+                } else {
+                    if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+                        // Status bar panels are the only windows who can go on top of the status
+                        // bar. They are protected by the STATUS_BAR_SERVICE permission, so they
+                        // have the same privileges as the status bar itself.
+                        //
+                        // However, they should still dodge the navigation bar if it exists.
+
+                        pf.left = df.left = of.left = hasNavBar
+                                ? displayFrames.mDock.left : displayFrames.mUnrestricted.left;
+                        pf.top = df.top = of.top = displayFrames.mUnrestricted.top;
+                        pf.right = df.right = of.right = hasNavBar
+                                ? displayFrames.mRestricted.right
+                                : displayFrames.mUnrestricted.right;
+                        pf.bottom = df.bottom = of.bottom = hasNavBar
+                                ? displayFrames.mRestricted.bottom
+                                : displayFrames.mUnrestricted.bottom;
+
+                        if (DEBUG_LAYOUT) Slog.v(TAG, "Laying out status bar window: " + pf);
+                    } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
+                            && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+                        // Asking to layout into the overscan region, so give it that pure
+                        // unrestricted area.
+                        of.set(displayFrames.mOverscan);
+                        df.set(displayFrames.mOverscan);
+                        pf.set(displayFrames.mOverscan);
+                    } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+                            && (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW
+                            || type == TYPE_VOLUME_OVERLAY)) {
+                        // Asking for layout as if the nav bar is hidden, lets the application
+                        // extend into the unrestricted overscan screen area. We only do this for
+                        // application windows and certain system windows to ensure no window that
+                        // can be above the nav bar can do this.
+                        df.set(displayFrames.mOverscan);
+                        pf.set(displayFrames.mOverscan);
+                        // We need to tell the app about where the frame inside the overscan is, so
+                        // it can inset its content by that amount -- it didn't ask to actually
+                        // extend itself into the overscan region.
+                        of.set(displayFrames.mUnrestricted);
+                    } else {
+                        df.set(displayFrames.mRestrictedOverscan);
+                        pf.set(displayFrames.mRestrictedOverscan);
+                        // We need to tell the app about where the frame inside the overscan
+                        // is, so it can inset its content by that amount -- it didn't ask
+                        // to actually extend itself into the overscan region.
+                        of.set(displayFrames.mUnrestricted);
+                    }
+
+                    if ((fl & FLAG_FULLSCREEN) == 0) {
+                        if (win.isVoiceInteraction()) {
+                            cf.set(displayFrames.mVoiceContent);
+                        } else {
+                            if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+                                cf.set(displayFrames.mDock);
+                            } else {
+                                cf.set(displayFrames.mContent);
+                            }
+                        }
+                    } else {
+                        // Full screen windows are always given a layout that is as if the status
+                        // bar and other transient decors are gone. This is to avoid bad states when
+                        // moving from a window that is not hiding the status bar to one that is.
+                        cf.set(displayFrames.mRestricted);
+                    }
+                    applyStableConstraints(sysUiFl, fl, cf, displayFrames);
+                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                        vf.set(displayFrames.mCurrent);
+                    } else {
+                        vf.set(cf);
+                    }
+
+                    // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+                    mExperiments.offsetWindowFramesForNavBar(mNavigationBarPosition, win);
+                    // EXPERIMENT END
+                }
+            } else if (layoutInScreen || (sysUiFl
+                    & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): IN_SCREEN");
+                // A window that has requested to fill the entire screen just
+                // gets everything, period.
+                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
+                    if (hasNavBar) {
+                        pf.left = df.left = of.left = cf.left = displayFrames.mDock.left;
+                        pf.right = df.right = of.right = cf.right = displayFrames.mRestricted.right;
+                        pf.bottom = df.bottom = of.bottom = cf.bottom =
+                                displayFrames.mRestricted.bottom;
+                    }
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "Laying out IN_SCREEN status bar window: " + pf);
+                } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
+                    // The navigation bar has Real Ultimate Power.
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "Laying out navigation bar window: " + pf);
+                } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
+                        && ((fl & FLAG_FULLSCREEN) != 0)) {
+                    // Fullscreen secure system overlays get what they ask for. Screenshot region
+                    // selection overlay should also expand to full screen.
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
+                } else if (type == TYPE_BOOT_PROGRESS) {
+                    // Boot progress screen always covers entire display.
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
+                } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
+                        && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
+                    // Asking to layout into the overscan region, so give it that pure unrestricted
+                    // area.
+                    cf.set(displayFrames.mOverscan);
+                    of.set(displayFrames.mOverscan);
+                    df.set(displayFrames.mOverscan);
+                    pf.set(displayFrames.mOverscan);
+                } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
+                        && (type == TYPE_STATUS_BAR
+                        || type == TYPE_TOAST
+                        || type == TYPE_DOCK_DIVIDER
+                        || type == TYPE_VOICE_INTERACTION_STARTING
+                        || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
+                    // Asking for layout as if the nav bar is hidden, lets the
+                    // application extend into the unrestricted screen area.  We
+                    // only do this for application windows (or toasts) to ensure no window that
+                    // can be above the nav bar can do this.
+                    // XXX This assumes that an app asking for this will also
+                    // ask for layout in only content.  We can't currently figure out
+                    // what the screen would be if only laying out to hide the nav bar.
+                    cf.set(displayFrames.mUnrestricted);
+                    of.set(displayFrames.mUnrestricted);
+                    df.set(displayFrames.mUnrestricted);
+                    pf.set(displayFrames.mUnrestricted);
+                } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
+                    if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+                        cf.set(displayFrames.mDock);
+                    } else {
+                        cf.set(displayFrames.mContent);
+                    }
+                } else {
+                    cf.set(displayFrames.mRestricted);
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
+                }
+
+                applyStableConstraints(sysUiFl, fl, cf, displayFrames);
+
+                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                    vf.set(displayFrames.mCurrent);
+                } else {
+                    vf.set(cf);
+                }
+            } else if (attached != null) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): attached to " + attached);
+                // A child window should be placed inside of the same visible
+                // frame that its parent had.
+                setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
+                        displayFrames);
+            } else {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+                        + "): normal window");
+                // Otherwise, a normal window must be placed inside the content
+                // of all screen decorations.
+                if (type == TYPE_STATUS_BAR_PANEL) {
+                    // Status bar panels can go on
+                    // top of the status bar. They are protected by the STATUS_BAR_SERVICE
+                    // permission, so they have the same privileges as the status bar itself.
+                    cf.set(displayFrames.mRestricted);
+                    of.set(displayFrames.mRestricted);
+                    df.set(displayFrames.mRestricted);
+                    pf.set(displayFrames.mRestricted);
+                } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
+                    // These dialogs are stable to interim decor changes.
+                    cf.set(displayFrames.mStable);
+                    of.set(displayFrames.mStable);
+                    df.set(displayFrames.mStable);
+                    pf.set(displayFrames.mStable);
+                } else {
+                    pf.set(displayFrames.mContent);
+                    if (win.isVoiceInteraction()) {
+                        cf.set(displayFrames.mVoiceContent);
+                        of.set(displayFrames.mVoiceContent);
+                        df.set(displayFrames.mVoiceContent);
+                    } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
+                        cf.set(displayFrames.mDock);
+                        of.set(displayFrames.mDock);
+                        df.set(displayFrames.mDock);
+                    } else {
+                        cf.set(displayFrames.mContent);
+                        of.set(displayFrames.mContent);
+                        df.set(displayFrames.mContent);
+                    }
+                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+                        vf.set(displayFrames.mCurrent);
+                    } else {
+                        vf.set(cf);
+                    }
+                }
+            }
+        }
+
+        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
+        final boolean attachedInParent = attached != null && !layoutInScreen;
+        final boolean requestedHideNavigation =
+                (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+
+        // TYPE_BASE_APPLICATION windows are never considered floating here because they don't get
+        // cropped / shifted to the displayFrame in WindowState.
+        final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
+                && type != TYPE_BASE_APPLICATION;
+
+        // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
+        // the cutout safe zone.
+        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
+            final Rect displayCutoutSafeExceptMaybeBars = sTmpDisplayCutoutSafeExceptMaybeBarsRect;
+            displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
+            if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
+                    && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
+                // At the top we have the status bar, so apps that are
+                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
+                // already expect that there's an inset there and we don't need to exclude
+                // the window from that area.
+                displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
+            }
+            if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
+                    && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
+                // Same for the navigation bar.
+                switch (mNavigationBarPosition) {
+                    case NAV_BAR_BOTTOM:
+                        displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+                        break;
+                    case NAV_BAR_RIGHT:
+                        displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
+                        break;
+                    case NAV_BAR_LEFT:
+                        displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
+                        break;
+                }
+            }
+            if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
+                // The IME can always extend under the bottom cutout if the navbar is there.
+                displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
+            }
+            // Windows that are attached to a parent and laid out in said parent already avoid
+            // the cutout according to that parent and don't need to be further constrained.
+            // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
+            // They will later be cropped or shifted using the displayFrame in WindowState,
+            // which prevents overlap with the DisplayCutout.
+            if (!attachedInParent && !floatingInScreenWindow) {
+                sTmpRect.set(pf);
+                pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
+                windowFrames.setParentFrameWasClippedByDisplayCutout(!sTmpRect.equals(pf));
+            }
+            // Make sure that NO_LIMITS windows clipped to the display don't extend under the
+            // cutout.
+            df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
+        }
+
+        // Content should never appear in the cutout.
+        cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
+
+        // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
+        // Also, we don't allow windows in multi-window mode to extend out of the screen.
+        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
+                && !win.isInMultiWindowMode()) {
+            df.left = df.top = -10000;
+            df.right = df.bottom = 10000;
+            if (type != TYPE_WALLPAPER) {
+                of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
+                of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
+            }
+        }
+
+        // If the device has a chin (e.g. some watches), a dead area at the bottom of the screen we
+        // need to provide information to the clients that want to pretend that you can draw there.
+        // We only want to apply outsets to certain types of windows. For example, we never want to
+        // apply the outsets to floating dialogs, because they wouldn't make sense there.
+        final boolean useOutsets = shouldUseOutsets(attrs, fl);
+        if (isDefaultDisplay && useOutsets) {
+            final Rect osf = windowFrames.mOutsetFrame;
+            osf.set(cf.left, cf.top, cf.right, cf.bottom);
+            windowFrames.setHasOutsets(true);
+            int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
+            if (outset > 0) {
+                int rotation = displayFrames.mRotation;
+                if (rotation == Surface.ROTATION_0) {
+                    osf.bottom += outset;
+                } else if (rotation == Surface.ROTATION_90) {
+                    osf.right += outset;
+                } else if (rotation == Surface.ROTATION_180) {
+                    osf.top -= outset;
+                } else if (rotation == Surface.ROTATION_270) {
+                    osf.left -= outset;
+                }
+                if (DEBUG_LAYOUT) Slog.v(TAG, "applying bottom outset of " + outset
+                        + " with rotation " + rotation + ", result: " + osf);
+            }
+        }
+
+        if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
+                + ": sim=#" + Integer.toHexString(sim)
+                + " attach=" + attached + " type=" + type
+                + String.format(" flags=0x%08x", fl)
+                + " pf=" + pf.toShortString() + " df=" + df.toShortString()
+                + " of=" + of.toShortString()
+                + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()
+                + " dcf=" + dcf.toShortString()
+                + " sf=" + sf.toShortString()
+                + " osf=" + windowFrames.mOutsetFrame.toShortString() + " " + win);
+
+        if (!sTmpLastParentFrame.equals(pf)) {
+            windowFrames.setContentChanged(true);
+        }
+
+        win.computeFrameLw();
+        // Dock windows carve out the bottom of the screen, so normal windows
+        // can't appear underneath them.
+        if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
+                && !win.getGivenInsetsPendingLw()) {
+            offsetInputMethodWindowLw(win, displayFrames);
+        }
+        if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
+                && !win.getGivenInsetsPendingLw()) {
+            offsetVoiceInputWindowLw(win, displayFrames);
+        }
+    }
+
+    private void layoutWallpaper(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect cf) {
+        // The wallpaper has Real Ultimate Power, but we want to tell it about the overscan area.
+        df.set(displayFrames.mOverscan);
+        pf.set(displayFrames.mOverscan);
+        cf.set(displayFrames.mUnrestricted);
+        of.set(displayFrames.mUnrestricted);
+    }
+
+    private void offsetInputMethodWindowLw(WindowState win, DisplayFrames displayFrames) {
+        int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
+        top += win.getGivenContentInsetsLw().top;
+        displayFrames.mContent.bottom = Math.min(displayFrames.mContent.bottom, top);
+        displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
+        top = win.getVisibleFrameLw().top;
+        top += win.getGivenVisibleInsetsLw().top;
+        displayFrames.mCurrent.bottom = Math.min(displayFrames.mCurrent.bottom, top);
+        if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
+                + displayFrames.mDock.bottom + " mContentBottom="
+                + displayFrames.mContent.bottom + " mCurBottom=" + displayFrames.mCurrent.bottom);
+    }
+
+    private void offsetVoiceInputWindowLw(WindowState win, DisplayFrames displayFrames) {
+        int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
+        top += win.getGivenContentInsetsLw().top;
+        displayFrames.mVoiceContent.bottom = Math.min(displayFrames.mVoiceContent.bottom, top);
+    }
+
+    /**
+     * Called following layout of all windows before each window has policy applied.
+     */
+    public void beginPostLayoutPolicyLw() {
+        mTopFullscreenOpaqueWindowState = null;
+        mTopFullscreenOpaqueOrDimmingWindowState = null;
+        mTopDockedOpaqueWindowState = null;
+        mTopDockedOpaqueOrDimmingWindowState = null;
+        mForceStatusBar = false;
+        mForceStatusBarFromKeyguard = false;
+        mForceStatusBarTransparent = false;
+        mForcingShowNavBar = false;
+        mForcingShowNavBarLayer = -1;
+
+        mAllowLockscreenWhenOn = false;
+        mShowingDream = false;
+        mWindowSleepTokenNeeded = false;
+    }
+
+    /**
+     * Called following layout of all window to apply policy to each window.
+     *
+     * @param win The window being positioned.
+     * @param attrs The LayoutParams of the window.
+     * @param attached For sub-windows, the window it is attached to. Otherwise null.
+     */
+    public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
+            WindowState attached, WindowState imeTarget) {
+        final boolean affectsSystemUi = win.canAffectSystemUiFlags();
+        if (DEBUG_LAYOUT) Slog.i(TAG, "Win " + win + ": affectsSystemUi=" + affectsSystemUi);
+        mService.mPolicy.applyKeyguardPolicyLw(win, imeTarget);
+        final int fl = PolicyControl.getWindowFlags(win, attrs);
+        if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi
+                && attrs.type == TYPE_INPUT_METHOD) {
+            mForcingShowNavBar = true;
+            mForcingShowNavBarLayer = win.getSurfaceLayer();
+        }
+        if (attrs.type == TYPE_STATUS_BAR) {
+            if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
+                mForceStatusBarFromKeyguard = true;
+            }
+            if ((attrs.privateFlags & PRIVATE_FLAG_FORCE_STATUS_BAR_VISIBLE_TRANSPARENT) != 0) {
+                mForceStatusBarTransparent = true;
+            }
+        }
+
+        boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
+                && attrs.type < FIRST_SYSTEM_WINDOW;
+        final int windowingMode = win.getWindowingMode();
+        final boolean inFullScreenOrSplitScreenSecondaryWindowingMode =
+                windowingMode == WINDOWING_MODE_FULLSCREEN
+                        || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        if (mTopFullscreenOpaqueWindowState == null && affectsSystemUi) {
+            if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
+                mForceStatusBar = true;
+            }
+            if (attrs.type == TYPE_DREAM) {
+                // If the lockscreen was showing when the dream started then wait
+                // for the dream to draw before hiding the lockscreen.
+                if (!mDreamingLockscreen
+                        || (win.isVisibleLw() && win.hasDrawnLw())) {
+                    mShowingDream = true;
+                    appWindow = true;
+                }
+            }
+
+            // For app windows that are not attached, we decide if all windows in the app they
+            // represent should be hidden or if we should hide the lockscreen. For attached app
+            // windows we defer the decision to the window it is attached to.
+            if (appWindow && attached == null) {
+                if (attrs.isFullscreen() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
+                    mTopFullscreenOpaqueWindowState = win;
+                    if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
+                        mTopFullscreenOpaqueOrDimmingWindowState = win;
+                    }
+                    if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
+                        mAllowLockscreenWhenOn = true;
+                    }
+                }
+            }
+        }
+
+        // Voice interaction overrides both top fullscreen and top docked.
+        if (affectsSystemUi && win.getAttrs().type == TYPE_VOICE_INTERACTION) {
+            if (mTopFullscreenOpaqueWindowState == null) {
+                mTopFullscreenOpaqueWindowState = win;
+                if (mTopFullscreenOpaqueOrDimmingWindowState == null) {
+                    mTopFullscreenOpaqueOrDimmingWindowState = win;
+                }
+            }
+            if (mTopDockedOpaqueWindowState == null) {
+                mTopDockedOpaqueWindowState = win;
+                if (mTopDockedOpaqueOrDimmingWindowState == null) {
+                    mTopDockedOpaqueOrDimmingWindowState = win;
+                }
+            }
+        }
+
+        // Keep track of the window if it's dimming but not necessarily fullscreen.
+        if (mTopFullscreenOpaqueOrDimmingWindowState == null && affectsSystemUi
+                && win.isDimming() && inFullScreenOrSplitScreenSecondaryWindowingMode) {
+            mTopFullscreenOpaqueOrDimmingWindowState = win;
+        }
+
+        // We need to keep track of the top "fullscreen" opaque window for the docked stack
+        // separately, because both the "real fullscreen" opaque window and the one for the docked
+        // stack can control View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.
+        if (mTopDockedOpaqueWindowState == null && affectsSystemUi && appWindow && attached == null
+                && attrs.isFullscreen() && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            mTopDockedOpaqueWindowState = win;
+            if (mTopDockedOpaqueOrDimmingWindowState == null) {
+                mTopDockedOpaqueOrDimmingWindowState = win;
+            }
+        }
+
+        // Also keep track of any windows that are dimming but not necessarily fullscreen in the
+        // docked stack.
+        if (mTopDockedOpaqueOrDimmingWindowState == null && affectsSystemUi && win.isDimming()
+                && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            mTopDockedOpaqueOrDimmingWindowState = win;
+        }
+
+        // Take note if a window wants to acquire a sleep token.
+        if ((attrs.privateFlags & PRIVATE_FLAG_ACQUIRES_SLEEP_TOKEN) != 0
+                && win.canAcquireSleepToken()) {
+            mWindowSleepTokenNeeded = true;
+        }
+    }
+
+    /**
+     * Called following layout of all windows and after policy has been applied
+     * to each window. If in this function you do
+     * something that may have modified the animation state of another window,
+     * be sure to return non-zero in order to perform another pass through layout.
+     *
+     * @return Return any bit set of
+     *         {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
+     *         {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
+     *         {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER}, or
+     *         {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
+     */
+    public int finishPostLayoutPolicyLw() {
+        int changes = 0;
+        boolean topIsFullscreen = false;
+
+        // If we are not currently showing a dream then remember the current
+        // lockscreen state.  We will use this to determine whether the dream
+        // started while the lockscreen was showing and remember this state
+        // while the dream is showing.
+        if (!mShowingDream) {
+            mDreamingLockscreen = mService.mPolicy.isKeyguardShowingAndNotOccluded();
+            if (mDreamingSleepTokenNeeded) {
+                mDreamingSleepTokenNeeded = false;
+                mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 0, 1).sendToTarget();
+            }
+        } else {
+            if (!mDreamingSleepTokenNeeded) {
+                mDreamingSleepTokenNeeded = true;
+                mHandler.obtainMessage(MSG_UPDATE_DREAMING_SLEEP_TOKEN, 1, 1).sendToTarget();
+            }
+        }
+
+        if (mStatusBar != null) {
+            if (DEBUG_LAYOUT) Slog.i(TAG, "force=" + mForceStatusBar
+                    + " forcefkg=" + mForceStatusBarFromKeyguard
+                    + " top=" + mTopFullscreenOpaqueWindowState);
+            boolean shouldBeTransparent = mForceStatusBarTransparent
+                    && !mForceStatusBar
+                    && !mForceStatusBarFromKeyguard;
+            if (!shouldBeTransparent) {
+                mStatusBarController.setShowTransparent(false /* transparent */);
+            } else if (!mStatusBar.isVisibleLw()) {
+                mStatusBarController.setShowTransparent(true /* transparent */);
+            }
+
+            boolean statusBarForcesShowingNavigation =
+                    (mStatusBar.getAttrs().privateFlags
+                            & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
+            boolean topAppHidesStatusBar = topAppHidesStatusBar();
+            if (mForceStatusBar || mForceStatusBarFromKeyguard || mForceStatusBarTransparent
+                    || statusBarForcesShowingNavigation) {
+                if (DEBUG_LAYOUT) Slog.v(TAG, "Showing status bar: forced");
+                if (mStatusBarController.setBarShowingLw(true)) {
+                    changes |= FINISH_LAYOUT_REDO_LAYOUT;
+                }
+                // Maintain fullscreen layout until incoming animation is complete.
+                topIsFullscreen = mTopIsFullscreen && mStatusBar.isAnimatingLw();
+                // Transient status bar is not allowed if status bar is on lockscreen or status bar
+                // is expecting the navigation keys from the user.
+                if ((mForceStatusBarFromKeyguard || statusBarForcesShowingNavigation)
+                        && mStatusBarController.isTransientShowing()) {
+                    mStatusBarController.updateVisibilityLw(false /*transientAllowed*/,
+                            mLastSystemUiFlags, mLastSystemUiFlags);
+                }
+            } else if (mTopFullscreenOpaqueWindowState != null) {
+                topIsFullscreen = topAppHidesStatusBar;
+                // The subtle difference between the window for mTopFullscreenOpaqueWindowState
+                // and mTopIsFullscreen is that mTopIsFullscreen is set only if the window
+                // has the FLAG_FULLSCREEN set.  Not sure if there is another way that to be the
+                // case though.
+                if (mStatusBarController.isTransientShowing()) {
+                    if (mStatusBarController.setBarShowingLw(true)) {
+                        changes |= FINISH_LAYOUT_REDO_LAYOUT;
+                    }
+                } else if (topIsFullscreen
+                        && !mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM)
+                        && !mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)) {
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "** HIDING status bar");
+                    if (mStatusBarController.setBarShowingLw(false)) {
+                        changes |= FINISH_LAYOUT_REDO_LAYOUT;
+                    } else {
+                        if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar already hiding");
+                    }
+                } else {
+                    if (DEBUG_LAYOUT) Slog.v(TAG, "** SHOWING status bar: top is not fullscreen");
+                    if (mStatusBarController.setBarShowingLw(true)) {
+                        changes |= FINISH_LAYOUT_REDO_LAYOUT;
+                    }
+                    topAppHidesStatusBar = false;
+                }
+            }
+            mStatusBarController.setTopAppHidesStatusBar(topAppHidesStatusBar);
+        }
+
+        if (mTopIsFullscreen != topIsFullscreen) {
+            if (!topIsFullscreen) {
+                // Force another layout when status bar becomes fully shown.
+                changes |= FINISH_LAYOUT_REDO_LAYOUT;
+            }
+            mTopIsFullscreen = topIsFullscreen;
+        }
+
+        if ((updateSystemUiVisibilityLw() & SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+            // If the navigation bar has been hidden or shown, we need to do another
+            // layout pass to update that window.
+            changes |= FINISH_LAYOUT_REDO_LAYOUT;
+        }
+
+        if (mShowingDream != mLastShowingDream) {
+            mLastShowingDream = mShowingDream;
+            mService.notifyShowingDreamChanged();
+        }
+
+        updateWindowSleepToken();
+
+        mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn);
+        return changes;
+    }
+
+    private void updateWindowSleepToken() {
+        if (mWindowSleepTokenNeeded && !mLastWindowSleepTokenNeeded) {
+            mHandler.removeCallbacks(mReleaseSleepTokenRunnable);
+            mHandler.post(mAcquireSleepTokenRunnable);
+        } else if (!mWindowSleepTokenNeeded && mLastWindowSleepTokenNeeded) {
+            mHandler.removeCallbacks(mAcquireSleepTokenRunnable);
+            mHandler.post(mReleaseSleepTokenRunnable);
+        }
+        mLastWindowSleepTokenNeeded = mWindowSleepTokenNeeded;
+    }
+
+    /**
+     * @return Whether the top app should hide the statusbar based on the top fullscreen opaque
+     *         window.
+     */
+    private boolean topAppHidesStatusBar() {
+        if (mTopFullscreenOpaqueWindowState == null) {
+            return false;
+        }
+        final int fl = PolicyControl.getWindowFlags(null,
+                mTopFullscreenOpaqueWindowState.getAttrs());
+        if (localLOGV) {
+            Slog.d(TAG, "frame: " + mTopFullscreenOpaqueWindowState.getFrameLw());
+            Slog.d(TAG, "attr: " + mTopFullscreenOpaqueWindowState.getAttrs()
+                    + " lp.flags=0x" + Integer.toHexString(fl));
+        }
+        return (fl & LayoutParams.FLAG_FULLSCREEN) != 0
+                || (mLastSystemUiFlags & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+    }
+
+    /**
+     * Called when the resource overlays change.
+     */
+    public void onOverlayChangedLw() {
+        onConfigurationChanged();
+    }
+
+    /**
+     * Called when the configuration has changed, and it's safe to load new values from resources.
+     */
+    public void onConfigurationChanged() {
+        final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+
+        final Context uiContext = getSystemUiContext();
+        final Resources res = uiContext.getResources();
+        final int portraitRotation = displayRotation.getPortraitRotation();
+        final int upsideDownRotation = displayRotation.getUpsideDownRotation();
+        final int landscapeRotation = displayRotation.getLandscapeRotation();
+        final int seascapeRotation = displayRotation.getSeascapeRotation();
+
+        mStatusBarHeightForRotation[portraitRotation] =
+        mStatusBarHeightForRotation[upsideDownRotation] =
+                res.getDimensionPixelSize(R.dimen.status_bar_height_portrait);
+        mStatusBarHeightForRotation[landscapeRotation] =
+        mStatusBarHeightForRotation[seascapeRotation] =
+                res.getDimensionPixelSize(R.dimen.status_bar_height_landscape);
+
+        // Height of the navigation bar when presented horizontally at bottom
+        mNavigationBarHeightForRotationDefault[portraitRotation] =
+        mNavigationBarHeightForRotationDefault[upsideDownRotation] =
+                res.getDimensionPixelSize(R.dimen.navigation_bar_height);
+        mNavigationBarHeightForRotationDefault[landscapeRotation] =
+        mNavigationBarHeightForRotationDefault[seascapeRotation] =
+                res.getDimensionPixelSize(R.dimen.navigation_bar_height_landscape);
+
+        // Width of the navigation bar when presented vertically along one side
+        mNavigationBarWidthForRotationDefault[portraitRotation] =
+        mNavigationBarWidthForRotationDefault[upsideDownRotation] =
+        mNavigationBarWidthForRotationDefault[landscapeRotation] =
+        mNavigationBarWidthForRotationDefault[seascapeRotation] =
+                res.getDimensionPixelSize(R.dimen.navigation_bar_width);
+
+        if (ALTERNATE_CAR_MODE_NAV_SIZE) {
+            // Height of the navigation bar when presented horizontally at bottom
+            mNavigationBarHeightForRotationInCarMode[portraitRotation] =
+            mNavigationBarHeightForRotationInCarMode[upsideDownRotation] =
+                    res.getDimensionPixelSize(R.dimen.navigation_bar_height_car_mode);
+            mNavigationBarHeightForRotationInCarMode[landscapeRotation] =
+            mNavigationBarHeightForRotationInCarMode[seascapeRotation] =
+                    res.getDimensionPixelSize(R.dimen.navigation_bar_height_landscape_car_mode);
+
+            // Width of the navigation bar when presented vertically along one side
+            mNavigationBarWidthForRotationInCarMode[portraitRotation] =
+            mNavigationBarWidthForRotationInCarMode[upsideDownRotation] =
+            mNavigationBarWidthForRotationInCarMode[landscapeRotation] =
+            mNavigationBarWidthForRotationInCarMode[seascapeRotation] =
+                    res.getDimensionPixelSize(R.dimen.navigation_bar_width_car_mode);
+        }
+
+        // EXPERIMENT TODO(b/113952590): Remove once experiment in bug is completed
+        mExperiments.onConfigurationChanged(uiContext);
+        // EXPERIMENT END
+    }
+
+    @VisibleForTesting
+    Context getSystemUiContext() {
+        final Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+        return mDisplayContent.isDefaultDisplay
+                ? uiContext : uiContext.createDisplayContext(mDisplayContent.getDisplay());
+    }
+
+    private int getNavigationBarWidth(int rotation, int uiMode) {
+        if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+            return mNavigationBarWidthForRotationInCarMode[rotation];
+        } else {
+            return mNavigationBarWidthForRotationDefault[rotation];
+        }
+    }
+
+    /**
+     * Return the display width available after excluding any screen
+     * decorations that could never be removed in Honeycomb. That is, system bar or
+     * button bar.
+     */
+    public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
+            DisplayCutout displayCutout) {
+        int width = fullWidth;
+        if (hasNavigationBar()) {
+            // For a basic navigation bar, when we are in landscape mode we place
+            // the navigation bar to the side.
+            if (navigationBarCanMove() && fullWidth > fullHeight) {
+                width -= getNavigationBarWidth(rotation, uiMode);
+            }
+        }
+        if (displayCutout != null) {
+            width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
+        }
+        return width;
+    }
+
+    private int getNavigationBarHeight(int rotation, int uiMode) {
+        if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
+            return mNavigationBarHeightForRotationInCarMode[rotation];
+        } else {
+            return mNavigationBarHeightForRotationDefault[rotation];
+        }
+    }
+
+    /**
+     * Return the display height available after excluding any screen
+     * decorations that could never be removed in Honeycomb. That is, system bar or
+     * button bar.
+     */
+    public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
+            DisplayCutout displayCutout) {
+        int height = fullHeight;
+        if (hasNavigationBar()) {
+            // For a basic navigation bar, when we are in portrait mode we place
+            // the navigation bar to the bottom.
+            if (!navigationBarCanMove() || fullWidth < fullHeight) {
+                height -= getNavigationBarHeight(rotation, uiMode);
+            }
+        }
+        if (displayCutout != null) {
+            height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom();
+        }
+        return height;
+    }
+
+    /**
+     * Return the available screen width that we should report for the
+     * configuration.  This must be no larger than
+     * {@link #getNonDecorDisplayWidth(int, int, int, int, DisplayCutout)}; it may be smaller
+     * than that to account for more transient decoration like a status bar.
+     */
+    public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
+            DisplayCutout displayCutout) {
+        return getNonDecorDisplayWidth(fullWidth, fullHeight, rotation, uiMode, displayCutout);
+    }
+
+    /**
+     * Return the available screen height that we should report for the
+     * configuration.  This must be no larger than
+     * {@link #getNonDecorDisplayHeight(int, int, int, int, DisplayCutout)}; it may be smaller
+     * than that to account for more transient decoration like a status bar.
+     */
+    public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
+            DisplayCutout displayCutout) {
+        // There is a separate status bar at the top of the display.  We don't count that as part
+        // of the fixed decor, since it can hide; however, for purposes of configurations,
+        // we do want to exclude it since applications can't generally use that part
+        // of the screen.
+        int statusBarHeight = mStatusBarHeightForRotation[rotation];
+        if (displayCutout != null) {
+            // If there is a cutout, it may already have accounted for some part of the status
+            // bar height.
+            statusBarHeight = Math.max(0, statusBarHeight - displayCutout.getSafeInsetTop());
+        }
+        return getNonDecorDisplayHeight(fullWidth, fullHeight, rotation, uiMode, displayCutout)
+                - statusBarHeight;
+    }
+
+    boolean isShowingDreamLw() {
+        return mShowingDream;
+    }
+
+    /**
+     * Calculates the stable insets without running a layout.
+     *
+     * @param displayRotation the current display rotation
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     * @param displayCutout the current display cutout
+     * @param outInsets the insets to return
+     */
+    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            DisplayCutout displayCutout, Rect outInsets) {
+        outInsets.setEmpty();
+
+        // Navigation bar and status bar.
+        getNonDecorInsetsLw(displayRotation, displayWidth, displayHeight, displayCutout, outInsets);
+        outInsets.top = Math.max(outInsets.top, mStatusBarHeightForRotation[displayRotation]);
+    }
+
+    /**
+     * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
+     * bar or button bar. See {@link #getNonDecorDisplayWidth}.
+     *
+     * @param displayRotation the current display rotation
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     * @param displayCutout the current display cutout
+     * @param outInsets the insets to return
+     */
+    public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            DisplayCutout displayCutout, Rect outInsets) {
+        outInsets.setEmpty();
+
+        // Only navigation bar
+        if (hasNavigationBar()) {
+            final int uiMode = mService.mPolicy.getUiMode();
+            int position = navigationBarPosition(displayWidth, displayHeight, displayRotation);
+            if (position == NAV_BAR_BOTTOM) {
+                outInsets.bottom = getNavigationBarHeight(displayRotation, uiMode);
+            } else if (position == NAV_BAR_RIGHT) {
+                outInsets.right = getNavigationBarWidth(displayRotation, uiMode);
+            } else if (position == NAV_BAR_LEFT) {
+                outInsets.left = getNavigationBarWidth(displayRotation, uiMode);
+            }
+        }
+
+        if (displayCutout != null) {
+            outInsets.left += displayCutout.getSafeInsetLeft();
+            outInsets.top += displayCutout.getSafeInsetTop();
+            outInsets.right += displayCutout.getSafeInsetRight();
+            outInsets.bottom += displayCutout.getSafeInsetBottom();
+        }
+    }
+
+    @NavigationBarPosition
+    int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
+        if (navigationBarCanMove() && displayWidth > displayHeight) {
+            if (displayRotation == Surface.ROTATION_270) {
+                return NAV_BAR_LEFT;
+            } else if (displayRotation == Surface.ROTATION_90) {
+                return NAV_BAR_RIGHT;
+            }
+        }
+        return NAV_BAR_BOTTOM;
+    }
+
+    /**
+     * @return The side of the screen where navigation bar is positioned.
+     * @see WindowManagerPolicyConstants#NAV_BAR_LEFT
+     * @see WindowManagerPolicyConstants#NAV_BAR_RIGHT
+     * @see WindowManagerPolicyConstants#NAV_BAR_BOTTOM
+     */
+    @NavigationBarPosition
+    public int getNavBarPosition() {
+        return mNavigationBarPosition;
+    }
+
+    /**
+     * A new window has been focused.
+     */
+    public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
+        mFocusedWindow = newFocus;
+        mLastFocusedWindow = lastFocus;
+        if ((updateSystemUiVisibilityLw() & SYSTEM_UI_CHANGING_LAYOUT) != 0) {
+            // If the navigation bar has been hidden or shown, we need to do another
+            // layout pass to update that window.
+            return FINISH_LAYOUT_REDO_LAYOUT;
+        }
+        return 0;
+    }
+
+    /**
+     * Return true if it is okay to perform animations for an app transition
+     * that is about to occur. You may return false for this if, for example,
+     * the dream window is currently displayed so the switch should happen
+     * immediately.
+     */
+    public boolean allowAppAnimationsLw() {
+        return !mShowingDream;
+    }
+
+    private void updateDreamingSleepToken(boolean acquire) {
+        if (acquire) {
+            final int displayId = getDisplayId();
+            if (mDreamingSleepToken == null) {
+                mDreamingSleepToken = mService.mAtmInternal.acquireSleepToken(
+                        "DreamOnDisplay" + displayId, displayId);
+            }
+        } else {
+            if (mDreamingSleepToken != null) {
+                mDreamingSleepToken.release();
+                mDreamingSleepToken = null;
+            }
+        }
+    }
+
+    private void requestTransientBars(WindowState swipeTarget) {
+        synchronized (mLock) {
+            if (!mService.mPolicy.isUserSetupComplete()) {
+                // Swipe-up for navigation bar is disabled during setup
+                return;
+            }
+            boolean sb = mStatusBarController.checkShowTransientBarLw();
+            boolean nb = mNavigationBarController.checkShowTransientBarLw()
+                    && !isNavBarEmpty(mLastSystemUiFlags);
+            if (sb || nb) {
+                // Don't show status bar when swiping on already visible navigation bar
+                if (!nb && swipeTarget == mNavigationBar) {
+                    if (DEBUG) Slog.d(TAG, "Not showing transient bar, wrong swipe target");
+                    return;
+                }
+                if (sb) mStatusBarController.showTransient();
+                if (nb) mNavigationBarController.showTransient();
+                mImmersiveModeConfirmation.confirmCurrentPrompt();
+                updateSystemUiVisibilityLw();
+            }
+        }
+    }
+
+    private void disposeInputConsumer(InputConsumer inputConsumer) {
+        if (inputConsumer != null) {
+            inputConsumer.dismiss();
+        }
+    }
+
+    private boolean isStatusBarKeyguard() {
+        return mStatusBar != null
+                && (mStatusBar.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0;
+    }
+
+    private boolean isKeyguardOccluded() {
+        // TODO (b/113840485): Handle per display keyguard.
+        return mService.mPolicy.isKeyguardOccluded();
+    }
+
+    void resetSystemUiVisibilityLw() {
+        mLastSystemUiFlags = 0;
+        updateSystemUiVisibilityLw();
+    }
+
+    private int updateSystemUiVisibilityLw() {
+        // If there is no window focused, there will be nobody to handle the events
+        // anyway, so just hang on in whatever state we're in until things settle down.
+        WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
+                : mTopFullscreenOpaqueWindowState;
+        if (winCandidate == null) {
+            return 0;
+        }
+
+        // The immersive mode confirmation should never affect the system bar visibility, otherwise
+        // it will unhide the navigation bar and hide itself.
+        if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
+
+            // The immersive mode confirmation took the focus from mLastFocusedWindow which was
+            // controlling the system ui visibility. So if mLastFocusedWindow can still receive
+            // keys, we let it keep controlling the visibility.
+            final boolean lastFocusCanReceiveKeys =
+                    (mLastFocusedWindow != null && mLastFocusedWindow.canReceiveKeys());
+            winCandidate = isStatusBarKeyguard() ? mStatusBar
+                    : lastFocusCanReceiveKeys ? mLastFocusedWindow
+                            : mTopFullscreenOpaqueWindowState;
+            if (winCandidate == null) {
+                return 0;
+            }
+        }
+        final WindowState win = winCandidate;
+        if ((win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 && isKeyguardOccluded()) {
+            // We are updating at a point where the keyguard has gotten
+            // focus, but we were last in a state where the top window is
+            // hiding it.  This is probably because the keyguard as been
+            // shown while the top window was displayed, so we want to ignore
+            // it here because this is just a very transient change and it
+            // will quickly lose focus once it correctly gets hidden.
+            return 0;
+        }
+
+        int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
+                & ~mResettingSystemUiFlags
+                & ~mForceClearedSystemUiFlags;
+        if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {
+            tmpVisibility
+                    &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);
+        }
+
+        final int fullscreenVisibility = updateLightStatusBarLw(0 /* vis */,
+                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
+        final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
+                mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
+        mService.getStackBounds(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
+        mService.getStackBounds(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
+        final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
+        final int diff = visibility ^ mLastSystemUiFlags;
+        final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
+        final int dockedDiff = dockedVisibility ^ mLastDockedStackSysUiFlags;
+        final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);
+        if (diff == 0 && fullscreenDiff == 0 && dockedDiff == 0 && mLastFocusNeedsMenu == needsMenu
+                && mFocusedApp == win.getAppToken()
+                && mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
+                && mLastDockedStackBounds.equals(mDockedStackBounds)) {
+            return 0;
+        }
+        mLastSystemUiFlags = visibility;
+        mLastFullscreenStackSysUiFlags = fullscreenVisibility;
+        mLastDockedStackSysUiFlags = dockedVisibility;
+        mLastFocusNeedsMenu = needsMenu;
+        mFocusedApp = win.getAppToken();
+        final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
+        final Rect dockedStackBounds = new Rect(mDockedStackBounds);
+        mHandler.post(() -> {
+            StatusBarManagerInternal statusBar = getStatusBarManagerInternal();
+            if (statusBar != null) {
+                final int displayId = getDisplayId();
+                statusBar.setSystemUiVisibility(displayId, visibility, fullscreenVisibility,
+                        dockedVisibility, 0xffffffff, fullscreenStackBounds,
+                        dockedStackBounds, win.toString());
+                statusBar.topAppWindowChanged(displayId, needsMenu);
+            }
+        });
+        return diff;
+    }
+
+    private int updateLightStatusBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming) {
+        final boolean onKeyguard = isStatusBarKeyguard() && !isKeyguardOccluded();
+        final WindowState statusColorWin = onKeyguard ? mStatusBar : opaqueOrDimming;
+        if (statusColorWin != null && (statusColorWin == opaque || onKeyguard)) {
+            // If the top fullscreen-or-dimming window is also the top fullscreen, respect
+            // its light flag.
+            vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
+                    & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+        } else if (statusColorWin != null && statusColorWin.isDimming()) {
+            // Otherwise if it's dimming, clear the light flag.
+            vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+        }
+        return vis;
+    }
+
+    @VisibleForTesting
+    @Nullable
+    static WindowState chooseNavigationColorWindowLw(WindowState opaque,
+            WindowState opaqueOrDimming, WindowState imeWindow,
+            @NavigationBarPosition int navBarPosition) {
+        // If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME
+        // window can be navigation color window.
+        final boolean imeWindowCanNavColorWindow = imeWindow != null
+                && imeWindow.isVisibleLw()
+                && navBarPosition == NAV_BAR_BOTTOM
+                && (PolicyControl.getWindowFlags(imeWindow, null)
+                & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+
+        if (opaque != null && opaqueOrDimming == opaque) {
+            // If the top fullscreen-or-dimming window is also the top fullscreen, respect it
+            // unless IME window is also eligible, since currently the IME window is always show
+            // above the opaque fullscreen app window, regardless of the IME target window.
+            // TODO(b/31559891): Maybe we need to revisit this condition once b/31559891 is fixed.
+            return imeWindowCanNavColorWindow ? imeWindow : opaque;
+        }
+
+        if (opaqueOrDimming == null || !opaqueOrDimming.isDimming()) {
+            // No dimming window is involved. Determine the result only with the IME window.
+            return imeWindowCanNavColorWindow ? imeWindow : null;
+        }
+
+        if (!imeWindowCanNavColorWindow) {
+            // No IME window is involved. Determine the result only with opaqueOrDimming.
+            return opaqueOrDimming;
+        }
+
+        // The IME window and the dimming window are competing.  Check if the dimming window can be
+        // IME target or not.
+        if (LayoutParams.mayUseInputMethod(PolicyControl.getWindowFlags(opaqueOrDimming, null))) {
+            // The IME window is above the dimming window.
+            return imeWindow;
+        } else {
+            // The dimming window is above the IME window.
+            return opaqueOrDimming;
+        }
+    }
+
+    @VisibleForTesting
+    static int updateLightNavigationBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming,
+            WindowState imeWindow, WindowState navColorWin) {
+
+        if (navColorWin != null) {
+            if (navColorWin == imeWindow || navColorWin == opaque) {
+                // Respect the light flag.
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+                vis |= PolicyControl.getSystemUiVisibility(navColorWin, null)
+                        & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) {
+                // Clear the light flag for dimming window.
+                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
+            }
+        }
+        return vis;
+    }
+
+    private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
+        final boolean dockedStackVisible =
+                mDisplayContent.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final boolean freeformStackVisible =
+                mDisplayContent.isStackVisible(WINDOWING_MODE_FREEFORM);
+        final boolean resizing = mDisplayContent.getDockedDividerController().isResizing();
+
+        // We need to force system bars when the docked stack is visible, when the freeform stack
+        // is visible but also when we are resizing for the transitions when docked stack
+        // visibility changes.
+        mForceShowSystemBars = dockedStackVisible || freeformStackVisible || resizing;
+        final boolean forceOpaqueStatusBar = mForceShowSystemBars && !mForceStatusBarFromKeyguard;
+
+        // apply translucent bar vis flags
+        WindowState fullscreenTransWin = isStatusBarKeyguard() && !isKeyguardOccluded()
+                ? mStatusBar
+                : mTopFullscreenOpaqueWindowState;
+        vis = mStatusBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
+        vis = mNavigationBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
+        final int dockedVis = mStatusBarController.applyTranslucentFlagLw(
+                mTopDockedOpaqueWindowState, 0, 0);
+
+        final boolean fullscreenDrawsStatusBarBackground =
+                drawsStatusBarBackground(vis, mTopFullscreenOpaqueWindowState);
+        final boolean dockedDrawsStatusBarBackground =
+                drawsStatusBarBackground(dockedVis, mTopDockedOpaqueWindowState);
+
+        // prevent status bar interaction from clearing certain flags
+        int type = win.getAttrs().type;
+        boolean statusBarHasFocus = type == TYPE_STATUS_BAR;
+        if (statusBarHasFocus && !isStatusBarKeyguard()) {
+            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE
+                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                    | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
+            if (isKeyguardOccluded()) {
+                flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
+            }
+            vis = (vis & ~flags) | (oldVis & flags);
+        }
+
+        if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
+            vis |= View.STATUS_BAR_TRANSPARENT;
+            vis &= ~View.STATUS_BAR_TRANSLUCENT;
+        } else if ((!areTranslucentBarsAllowed() && fullscreenTransWin != mStatusBar)
+                || forceOpaqueStatusBar) {
+            vis &= ~(View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT);
+        }
+
+        vis = configureNavBarOpacity(vis, dockedStackVisible, freeformStackVisible, resizing);
+
+        // update status bar
+        boolean immersiveSticky =
+                (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+        final boolean hideStatusBarWM =
+                mTopFullscreenOpaqueWindowState != null
+                        && (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
+                        & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
+        final boolean hideStatusBarSysui =
+                (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
+        final boolean hideNavBarSysui =
+                (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
+
+        final boolean transientStatusBarAllowed = mStatusBar != null
+                && (statusBarHasFocus || (!mForceShowSystemBars
+                && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));
+
+        final boolean transientNavBarAllowed = mNavigationBar != null
+                && !mForceShowSystemBars && hideNavBarSysui && immersiveSticky;
+
+        final long now = SystemClock.uptimeMillis();
+        final boolean pendingPanic = mPendingPanicGestureUptime != 0
+                && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
+        final DisplayPolicy defaultDisplayPolicy =
+                mService.getDefaultDisplayContentLocked().getDisplayPolicy();
+        if (pendingPanic && hideNavBarSysui && !isStatusBarKeyguard()
+                // TODO (b/111955725): Show keyguard presentation on all external displays
+                && defaultDisplayPolicy.isKeyguardDrawComplete()) {
+            // The user performed the panic gesture recently, we're about to hide the bars,
+            // we're no longer on the Keyguard and the screen is ready. We can now request the bars.
+            mPendingPanicGestureUptime = 0;
+            mStatusBarController.showTransient();
+            if (!isNavBarEmpty(vis)) {
+                mNavigationBarController.showTransient();
+            }
+        }
+
+        final boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
+                && !transientStatusBarAllowed && hideStatusBarSysui;
+        final boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
+                && !transientNavBarAllowed;
+        if (denyTransientStatus || denyTransientNav || mForceShowSystemBars) {
+            // clear the clearable flags instead
+            clearClearableFlagsLw();
+            vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
+        }
+
+        final boolean immersive = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
+        immersiveSticky = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
+        final boolean navAllowedHidden = immersive || immersiveSticky;
+
+        if (hideNavBarSysui && !navAllowedHidden
+                && mService.mPolicy.getWindowLayerLw(win)
+                        > mService.mPolicy.getWindowLayerFromTypeLw(TYPE_INPUT_CONSUMER)) {
+            // We can't hide the navbar from this window otherwise the input consumer would not get
+            // the input events.
+            vis = (vis & ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
+        }
+
+        vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);
+
+        // update navigation bar
+        boolean oldImmersiveMode = isImmersiveMode(oldVis);
+        boolean newImmersiveMode = isImmersiveMode(vis);
+        if (oldImmersiveMode != newImmersiveMode) {
+            final String pkg = win.getOwningPackage();
+            mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode,
+                    mService.mPolicy.isUserSetupComplete(),
+                    isNavBarEmpty(win.getSystemUiVisibility()));
+        }
+
+        vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);
+
+        final WindowState navColorWin = chooseNavigationColorWindowLw(
+                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
+                mDisplayContent.mInputMethodWindow, mNavigationBarPosition);
+        vis = updateLightNavigationBarLw(vis, mTopFullscreenOpaqueWindowState,
+                mTopFullscreenOpaqueOrDimmingWindowState,
+                mDisplayContent.mInputMethodWindow, navColorWin);
+
+        return vis;
+    }
+
+    private boolean drawsStatusBarBackground(int vis, WindowState win) {
+        if (!mStatusBarController.isTransparentAllowed(win)) {
+            return false;
+        }
+        if (win == null) {
+            return true;
+        }
+
+        final boolean drawsSystemBars =
+                (win.getAttrs().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
+        final boolean forceDrawsSystemBars =
+                (win.getAttrs().privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+
+        return forceDrawsSystemBars || drawsSystemBars && (vis & View.STATUS_BAR_TRANSLUCENT) == 0;
+    }
+
+    /**
+     * @return the current visibility flags with the nav-bar opacity related flags toggled based
+     *         on the nav bar opacity rules chosen by {@link #mNavBarOpacityMode}.
+     */
+    private int configureNavBarOpacity(int visibility, boolean dockedStackVisible,
+            boolean freeformStackVisible, boolean isDockedDividerResizing) {
+        if (mNavBarOpacityMode == NAV_BAR_OPAQUE_WHEN_FREEFORM_OR_DOCKED) {
+            if (dockedStackVisible || freeformStackVisible || isDockedDividerResizing) {
+                visibility = setNavBarOpaqueFlag(visibility);
+            }
+        } else if (mNavBarOpacityMode == NAV_BAR_TRANSLUCENT_WHEN_FREEFORM_OPAQUE_OTHERWISE) {
+            if (isDockedDividerResizing) {
+                visibility = setNavBarOpaqueFlag(visibility);
+            } else if (freeformStackVisible) {
+                visibility = setNavBarTranslucentFlag(visibility);
+            } else {
+                visibility = setNavBarOpaqueFlag(visibility);
+            }
+        }
+
+        if (!areTranslucentBarsAllowed()) {
+            visibility &= ~View.NAVIGATION_BAR_TRANSLUCENT;
+        }
+        return visibility;
+    }
+
+    private int setNavBarOpaqueFlag(int visibility) {
+        return visibility & ~(View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT);
+    }
+
+    private int setNavBarTranslucentFlag(int visibility) {
+        visibility &= ~View.NAVIGATION_BAR_TRANSPARENT;
+        return visibility | View.NAVIGATION_BAR_TRANSLUCENT;
+    }
+
+    private void clearClearableFlagsLw() {
+        int newVal = mResettingSystemUiFlags | View.SYSTEM_UI_CLEARABLE_FLAGS;
+        if (newVal != mResettingSystemUiFlags) {
+            mResettingSystemUiFlags = newVal;
+            mDisplayContent.reevaluateStatusBarVisibility();
+        }
+    }
+
+    private boolean isImmersiveMode(int vis) {
+        final int flags = View.SYSTEM_UI_FLAG_IMMERSIVE | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+        return mNavigationBar != null
+                && (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
+                && (vis & flags) != 0
+                && canHideNavigationBar();
+    }
+
+    /**
+     * @return whether the navigation bar can be hidden, e.g. the device has a navigation bar
+     */
+    private boolean canHideNavigationBar() {
+        return hasNavigationBar();
+    }
+
+    private static boolean isNavBarEmpty(int systemUiFlags) {
+        final int disableNavigationBar = (View.STATUS_BAR_DISABLE_HOME
+                | View.STATUS_BAR_DISABLE_BACK
+                | View.STATUS_BAR_DISABLE_RECENT);
+
+        return (systemUiFlags & disableNavigationBar) == disableNavigationBar;
+    }
+
+    /**
+     * @return whether the navigation or status bar can be made translucent
+     *
+     * This should return true unless touch exploration is not enabled or
+     * R.boolean.config_enableTranslucentDecor is false.
+     */
+    private boolean areTranslucentBarsAllowed() {
+        return mTranslucentDecorEnabled;
+    }
+
+    boolean shouldRotateSeamlessly(DisplayRotation displayRotation, int oldRotation,
+            int newRotation) {
+        // For the upside down rotation we don't rotate seamlessly as the navigation
+        // bar moves position.
+        // Note most apps (using orientation:sensor or user as opposed to fullSensor)
+        // will not enter the reverse portrait orientation, so actually the
+        // orientation won't change at all.
+        if (oldRotation == displayRotation.getUpsideDownRotation()
+                || newRotation == displayRotation.getUpsideDownRotation()) {
+            return false;
+        }
+        // If the navigation bar can't change sides, then it will
+        // jump when we change orientations and we don't rotate
+        // seamlessly.
+        if (!navigationBarCanMove()) {
+            return false;
+        }
+
+        final WindowState w = mTopFullscreenOpaqueWindowState;
+        if (w != mFocusedWindow) {
+            return false;
+        }
+
+        // We only enable seamless rotation if the top window has requested
+        // it and is in the fullscreen opaque state. Seamless rotation
+        // requires freezing various Surface states and won't work well
+        // with animations, so we disable it in the animation case for now.
+        if (w != null && !w.isAnimatingLw()
+                && w.getAttrs().rotationAnimation == ROTATION_ANIMATION_SEAMLESS) {
+            return true;
+        }
+        return false;
+    }
+
+    private final Runnable mHiddenNavPanic = new Runnable() {
+        @Override
+        public void run() {
+            synchronized (mLock) {
+                if (!mService.mPolicy.isUserSetupComplete()) {
+                    // Swipe-up for navigation bar is disabled during setup
+                    return;
+                }
+                mPendingPanicGestureUptime = SystemClock.uptimeMillis();
+                if (!isNavBarEmpty(mLastSystemUiFlags)) {
+                    mNavigationBarController.showTransient();
+                }
+            }
+        }
+    };
+
+    void onPowerKeyDown(boolean isScreenOn) {
+        // Detect user pressing the power button in panic when an application has
+        // taken over the whole screen.
+        boolean panic = mImmersiveModeConfirmation.onPowerKeyDown(isScreenOn,
+                SystemClock.elapsedRealtime(), isImmersiveMode(mLastSystemUiFlags),
+                isNavBarEmpty(mLastSystemUiFlags));
+        if (panic) {
+            mHandler.post(mHiddenNavPanic);
+        }
+    }
+
+    void onVrStateChangedLw(boolean enabled) {
+        mImmersiveModeConfirmation.onVrStateChangedLw(enabled);
+    }
+
+    /**
+     * Called when the state of lock task mode changes. This should be used to disable immersive
+     * mode confirmation.
+     *
+     * @param lockTaskState the new lock task mode state. One of
+     *                      {@link ActivityManager#LOCK_TASK_MODE_NONE},
+     *                      {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
+     *                      {@link ActivityManager#LOCK_TASK_MODE_PINNED}.
+     */
+    public void onLockTaskStateChangedLw(int lockTaskState) {
+        mImmersiveModeConfirmation.onLockTaskModeChangedLw(lockTaskState);
+    }
+
+    /**
+     * Request a screenshot be taken.
+     *
+     * @param screenshotType The type of screenshot, for example either
+     *                       {@link WindowManager#TAKE_SCREENSHOT_FULLSCREEN} or
+     *                       {@link WindowManager#TAKE_SCREENSHOT_SELECTED_REGION}
+     */
+    public void takeScreenshot(int screenshotType) {
+        if (mScreenshotHelper != null) {
+            mScreenshotHelper.takeScreenshot(screenshotType,
+                    mStatusBar != null && mStatusBar.isVisibleLw(),
+                    mNavigationBar != null && mNavigationBar.isVisibleLw(), mHandler);
+        }
+    }
+
     void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DisplayPolicy");
-        pw.print(prefix + "  mCarDockEnablesAccelerometer=" + mCarDockEnablesAccelerometer);
-        pw.println(" mDeskDockEnablesAccelerometer=" + mDeskDockEnablesAccelerometer);
-        pw.print(prefix + "  mDockMode=" + Intent.dockStateToString(mDockMode));
-        pw.println(" mLidState=" + WindowManagerFuncs.lidStateToString(mLidState));
-        pw.print(prefix + "  mAwake=" + mAwake);
-        pw.print(" mScreenOnEarly=" + mScreenOnEarly);
-        pw.println(" mScreenOnFully=" + mScreenOnFully);
-        pw.print(prefix + "  mKeyguardDrawComplete=" + mKeyguardDrawComplete);
-        pw.println(" mWindowManagerDrawComplete=" + mWindowManagerDrawComplete);
-        pw.println(prefix + "  mHdmiPlugged=" + mHdmiPlugged);
+        pw.print(prefix); pw.print("DisplayPolicy");
+        prefix += "  ";
+        pw.print(prefix);
+        pw.print("mCarDockEnablesAccelerometer="); pw.print(mCarDockEnablesAccelerometer);
+        pw.print(" mDeskDockEnablesAccelerometer=");
+        pw.println(mDeskDockEnablesAccelerometer);
+        pw.print(prefix); pw.print("mDockMode="); pw.print(Intent.dockStateToString(mDockMode));
+        pw.print(" mLidState="); pw.println(WindowManagerFuncs.lidStateToString(mLidState));
+        pw.print(prefix); pw.print("mAwake="); pw.print(mAwake);
+        pw.print(" mScreenOnEarly="); pw.print(mScreenOnEarly);
+        pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
+        pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
+        pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
+        pw.print(prefix); pw.print("mHdmiPlugged="); pw.println(mHdmiPlugged);
+        if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
+                || mForceClearedSystemUiFlags != 0) {
+            pw.print(prefix); pw.print("mLastSystemUiFlags=0x");
+            pw.print(Integer.toHexString(mLastSystemUiFlags));
+            pw.print(" mResettingSystemUiFlags=0x");
+            pw.print(Integer.toHexString(mResettingSystemUiFlags));
+            pw.print(" mForceClearedSystemUiFlags=0x");
+            pw.println(Integer.toHexString(mForceClearedSystemUiFlags));
+        }
+        if (mLastFocusNeedsMenu) {
+            pw.print(prefix); pw.print("mLastFocusNeedsMenu="); pw.println(mLastFocusNeedsMenu);
+        }
+        pw.print(prefix); pw.print("mShowingDream="); pw.print(mShowingDream);
+        pw.print(" mDreamingLockscreen="); pw.print(mDreamingLockscreen);
+        pw.print(" mDreamingSleepToken="); pw.println(mDreamingSleepToken);
+        if (mStatusBar != null) {
+            pw.print(prefix); pw.print("mStatusBar="); pw.print(mStatusBar);
+                    pw.print(" isStatusBarKeyguard="); pw.println(isStatusBarKeyguard());
+        }
+        if (mNavigationBar != null) {
+            pw.print(prefix); pw.print("mNavigationBar="); pw.println(mNavigationBar);
+        }
+        if (mFocusedWindow != null) {
+            pw.print(prefix); pw.print("mFocusedWindow="); pw.println(mFocusedWindow);
+        }
+        if (mFocusedApp != null) {
+            pw.print(prefix); pw.print("mFocusedApp="); pw.println(mFocusedApp);
+        }
+        if (mTopFullscreenOpaqueWindowState != null) {
+            pw.print(prefix); pw.print("mTopFullscreenOpaqueWindowState=");
+            pw.println(mTopFullscreenOpaqueWindowState);
+        }
+        if (mTopFullscreenOpaqueOrDimmingWindowState != null) {
+            pw.print(prefix); pw.print("mTopFullscreenOpaqueOrDimmingWindowState=");
+            pw.println(mTopFullscreenOpaqueOrDimmingWindowState);
+        }
+        if (mForcingShowNavBar) {
+            pw.print(prefix); pw.print("mForcingShowNavBar="); pw.println(mForcingShowNavBar);
+            pw.print(prefix); pw.print("mForcingShowNavBarLayer=");
+            pw.println(mForcingShowNavBarLayer);
+        }
+        pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
+        pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
+        pw.print(" mForceStatusBarFromKeyguard="); pw.println(mForceStatusBarFromKeyguard);
+        pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn);
+        mStatusBarController.dump(pw, prefix);
+        mNavigationBarController.dump(pw, prefix);
+
+        pw.print(prefix); pw.println("Looper state:");
+        mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + "  ");
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 6ab7090..f1d1e49 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -844,9 +844,9 @@
         pw.print(prefix + "  mPortraitRotation=" + Surface.rotationToString(mPortraitRotation));
         pw.println(" mUpsideDownRotation=" + Surface.rotationToString(mUpsideDownRotation));
 
-        pw.print(prefix + "  mSupportAutoRotation=" + mSupportAutoRotation);
+        pw.println(prefix + "  mSupportAutoRotation=" + mSupportAutoRotation);
         if (mOrientationListener != null) {
-            pw.print(" mOrientationSensorEnabled=" + mOrientationListener.mEnabled);
+            mOrientationListener.dump(pw, prefix + "  ");
         }
         pw.println();
 
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index c377072..7ea88bb 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -28,6 +28,8 @@
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
 import static android.view.WindowManager.TRANSIT_NONE;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
 
 import static com.android.server.wm.AppTransition.DEFAULT_APP_TRANSITION_DURATION;
 import static com.android.server.wm.AppTransition.TOUCH_RESPONSE_INTERPOLATOR;
@@ -52,6 +54,7 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.server.LocalServices;
@@ -184,8 +187,8 @@
                     .calculateNonDismissingSnapTarget(position).position;
             DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, mTmpRect,
                     mTmpRect2.width(), mTmpRect2.height(), getContentWidth());
-            mService.mPolicy.getStableInsetsLw(rotation, mTmpRect2.width(), mTmpRect2.height(),
-                    displayCutout, mTmpRect3);
+            mDisplayContent.getDisplayPolicy().getStableInsetsLw(rotation, mTmpRect2.width(),
+                    mTmpRect2.height(), displayCutout, mTmpRect3);
             mService.intersectDisplayInsetBounds(mTmpRect2, mTmpRect3, mTmpRect);
             minWidth = Math.min(mTmpRect.width(), minWidth);
         }
@@ -231,8 +234,9 @@
         final DisplayCutout displayCutout = mDisplayContent.getDisplayInfo().displayCutout;
         final int displayWidth = parentConfig.windowConfiguration.getBounds().width();
         final int displayHeight = parentConfig.windowConfiguration.getBounds().height();
-        mService.mPolicy.getStableInsetsLw(parentConfig.windowConfiguration.getRotation(),
-                displayWidth, displayHeight, displayCutout, mTmpRect);
+        mDisplayContent.getDisplayPolicy().getStableInsetsLw(
+                parentConfig.windowConfiguration.getRotation(), displayWidth, displayHeight,
+                displayCutout, mTmpRect);
         int dividerSize = mDividerWindowWidth - 2 * mDividerInsets;
         // The offset in the left (landscape)/top (portrait) is calculated with the minimized
         // offset value with the divider size and any system insets in that direction.
@@ -240,7 +244,7 @@
             outBounds.set(0, mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top,
                     displayWidth, displayHeight);
         } else {
-            // In landscape also inset the left/right side with the statusbar height to match the
+            // In landscape also inset the left/right side with the status bar height to match the
             // minimized size height in portrait mode.
             final int primaryTaskWidth = mTaskHeightInMinimizedMode + dividerSize + mTmpRect.top;
             int left = mTmpRect.left;
@@ -278,16 +282,16 @@
                     : mDisplayContent.mBaseDisplayHeight;
             final DisplayCutout displayCutout =
                     mDisplayContent.calculateDisplayCutoutForRotation(rotation).getDisplayCutout();
-            mService.mPolicy.getStableInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
+            final DisplayPolicy displayPolicy =  mDisplayContent.getDisplayPolicy();
+            displayPolicy.getStableInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
             config.unset();
             config.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
 
-            final int displayId = mDisplayContent.getDisplayId();
-            final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, rotation,
-                baseConfig.uiMode, displayId, displayCutout);
-            final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
-                baseConfig.uiMode, displayId, displayCutout);
-            mService.mPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
+            final int appWidth = displayPolicy.getNonDecorDisplayWidth(dw, dh, rotation,
+                    baseConfig.uiMode, displayCutout);
+            final int appHeight = displayPolicy.getNonDecorDisplayHeight(dw, dh, rotation,
+                    baseConfig.uiMode, displayCutout);
+            displayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
             final int leftInset = mTmpRect.left;
             final int topInset = mTmpRect.top;
 
@@ -295,10 +299,10 @@
                     leftInset + appWidth /*right*/, topInset + appHeight /*bottom*/);
 
             final float density = mDisplayContent.getDisplayMetrics().density;
-            config.screenWidthDp = (int) (mService.mPolicy.getConfigDisplayWidth(dw, dh,
-                    rotation, baseConfig.uiMode, displayId, displayCutout) / density);
-            config.screenHeightDp = (int) (mService.mPolicy.getConfigDisplayHeight(dw, dh,
-                    rotation, baseConfig.uiMode, displayId, displayCutout) / density);
+            config.screenWidthDp = (int) (displayPolicy.getConfigDisplayWidth(dw, dh, rotation,
+                    baseConfig.uiMode, displayCutout) / density);
+            config.screenHeightDp = (int) (displayPolicy.getConfigDisplayHeight(dw, dh, rotation,
+                    baseConfig.uiMode, displayCutout) / density);
             final Context rotationContext = mService.mContext.createConfigurationContext(config);
             mSnapAlgorithmForRotation[rotation] = new DividerSnapAlgorithm(
                     rotationContext.getResources(), dw, dh, getContentWidth(),
@@ -464,8 +468,32 @@
      * @return true if the side provided is valid
      */
     boolean canPrimaryStackDockTo(int dockSide, Rect parentRect, int rotation) {
-        return mService.mPolicy.isDockSideAllowed(dockSide, mOriginalDockedSide,
-                parentRect.width(), parentRect.height(), rotation);
+        final DisplayPolicy policy = mDisplayContent.getDisplayPolicy();
+        return isDockSideAllowed(dockSide, mOriginalDockedSide,
+                policy.navigationBarPosition(parentRect.width(), parentRect.height(), rotation),
+                policy.navigationBarCanMove());
+    }
+
+    @VisibleForTesting
+    static boolean isDockSideAllowed(int dockSide, int originalDockSide, int navBarPosition,
+            boolean navigationBarCanMove) {
+        if (dockSide == DOCKED_TOP) {
+            return true;
+        }
+
+        if (navigationBarCanMove) {
+            // Only allow the dockside opposite to the nav bar position in landscape
+            return dockSide == DOCKED_LEFT && navBarPosition == NAV_BAR_RIGHT
+                    || dockSide == DOCKED_RIGHT && navBarPosition == NAV_BAR_LEFT;
+        }
+
+        // Side is the same as original side
+        if (dockSide == originalDockSide) {
+            return true;
+        }
+
+        // Only if original docked side was top in portrait will allow left for landscape
+        return dockSide == DOCKED_LEFT && originalDockSide == DOCKED_TOP;
     }
 
     void notifyDockedStackExistsChanged(boolean exists) {
diff --git a/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
new file mode 100644
index 0000000..3d20501
--- /dev/null
+++ b/services/core/java/com/android/server/wm/ImmersiveModeConfirmation.java
@@ -0,0 +1,402 @@
+/*
+ * 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;
+
+import static android.app.ActivityManager.LOCK_TASK_MODE_LOCKED;
+import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.animation.ArgbEvaluator;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.ActivityThread;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.view.Display;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+import com.android.internal.R;
+
+/**
+ *  Helper to manage showing/hiding a confirmation prompt when the navigation bar is hidden
+ *  entering immersive mode.
+ */
+public class ImmersiveModeConfirmation {
+    private static final String TAG = "ImmersiveModeConfirmation";
+    private static final boolean DEBUG = false;
+    private static final boolean DEBUG_SHOW_EVERY_TIME = false; // super annoying, use with caution
+    private static final String CONFIRMED = "confirmed";
+
+    private static boolean sConfirmed;
+
+    private final Context mContext;
+    private final H mHandler;
+    private final long mShowDelayMs;
+    private final long mPanicThresholdMs;
+    private final IBinder mWindowToken = new Binder();
+
+    private ClingWindowView mClingWindow;
+    private long mPanicTime;
+    private WindowManager mWindowManager;
+    // Local copy of vr mode enabled state, to avoid calling into VrManager with
+    // the lock held.
+    private boolean mVrModeEnabled;
+    private int mLockTaskState = LOCK_TASK_MODE_NONE;
+
+    ImmersiveModeConfirmation(Context context, Looper looper, boolean vrModeEnabled) {
+        final Display display = context.getDisplay();
+        final Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+        mContext = display.getDisplayId() == DEFAULT_DISPLAY
+                ? uiContext : uiContext.createDisplayContext(display);
+        mHandler = new H(looper);
+        mShowDelayMs = getNavBarExitDuration() * 3;
+        mPanicThresholdMs = context.getResources()
+                .getInteger(R.integer.config_immersive_mode_confirmation_panic);
+        mWindowManager = (WindowManager)
+                mContext.getSystemService(Context.WINDOW_SERVICE);
+        mVrModeEnabled = vrModeEnabled;
+    }
+
+    private long getNavBarExitDuration() {
+        Animation exit = AnimationUtils.loadAnimation(mContext, R.anim.dock_bottom_exit);
+        return exit != null ? exit.getDuration() : 0;
+    }
+
+    static boolean loadSetting(int currentUserId, Context context) {
+        final boolean wasConfirmed = sConfirmed;
+        sConfirmed = false;
+        if (DEBUG) Slog.d(TAG, String.format("loadSetting() currentUserId=%d", currentUserId));
+        String value = null;
+        try {
+            value = Settings.Secure.getStringForUser(context.getContentResolver(),
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                    UserHandle.USER_CURRENT);
+            sConfirmed = CONFIRMED.equals(value);
+            if (DEBUG) Slog.d(TAG, "Loaded sConfirmed=" + sConfirmed);
+        } catch (Throwable t) {
+            Slog.w(TAG, "Error loading confirmations, value=" + value, t);
+        }
+        return sConfirmed != wasConfirmed;
+    }
+
+    private static void saveSetting(Context context) {
+        if (DEBUG) Slog.d(TAG, "saveSetting()");
+        try {
+            final String value = sConfirmed ? CONFIRMED : null;
+            Settings.Secure.putStringForUser(context.getContentResolver(),
+                    Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS,
+                    value,
+                    UserHandle.USER_CURRENT);
+            if (DEBUG) Slog.d(TAG, "Saved value=" + value);
+        } catch (Throwable t) {
+            Slog.w(TAG, "Error saving confirmations, sConfirmed=" + sConfirmed, t);
+        }
+    }
+
+    void immersiveModeChangedLw(String pkg, boolean isImmersiveMode,
+            boolean userSetupComplete, boolean navBarEmpty) {
+        mHandler.removeMessages(H.SHOW);
+        if (isImmersiveMode) {
+            final boolean disabled = PolicyControl.disableImmersiveConfirmation(pkg);
+            if (DEBUG) Slog.d(TAG, String.format("immersiveModeChanged() disabled=%s sConfirmed=%s",
+                    disabled, sConfirmed));
+            if (!disabled
+                    && (DEBUG_SHOW_EVERY_TIME || !sConfirmed)
+                    && userSetupComplete
+                    && !mVrModeEnabled
+                    && !navBarEmpty
+                    && !UserManager.isDeviceInDemoMode(mContext)
+                    && (mLockTaskState != LOCK_TASK_MODE_LOCKED)) {
+                mHandler.sendEmptyMessageDelayed(H.SHOW, mShowDelayMs);
+            }
+        } else {
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+    }
+
+    boolean onPowerKeyDown(boolean isScreenOn, long time, boolean inImmersiveMode,
+            boolean navBarEmpty) {
+        if (!isScreenOn && (time - mPanicTime < mPanicThresholdMs)) {
+            // turning the screen back on within the panic threshold
+            return mClingWindow == null;
+        }
+        if (isScreenOn && inImmersiveMode && !navBarEmpty) {
+            // turning the screen off, remember if we were in immersive mode
+            mPanicTime = time;
+        } else {
+            mPanicTime = 0;
+        }
+        return false;
+    }
+
+    void confirmCurrentPrompt() {
+        if (mClingWindow != null) {
+            if (DEBUG) Slog.d(TAG, "confirmCurrentPrompt()");
+            mHandler.post(mConfirm);
+        }
+    }
+
+    private void handleHide() {
+        if (mClingWindow != null) {
+            if (DEBUG) Slog.d(TAG, "Hiding immersive mode confirmation");
+            mWindowManager.removeView(mClingWindow);
+            mClingWindow = null;
+        }
+    }
+
+    private WindowManager.LayoutParams getClingWindowLayoutParams() {
+        final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
+                WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
+                PixelFormat.TRANSLUCENT);
+        lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
+        lp.setTitle("ImmersiveModeConfirmation");
+        lp.windowAnimations = com.android.internal.R.style.Animation_ImmersiveModeConfirmation;
+        lp.token = getWindowToken();
+        return lp;
+    }
+
+    private FrameLayout.LayoutParams getBubbleLayoutParams() {
+        return new FrameLayout.LayoutParams(
+                mContext.getResources().getDimensionPixelSize(
+                        R.dimen.immersive_mode_cling_width),
+                ViewGroup.LayoutParams.WRAP_CONTENT,
+                Gravity.CENTER_HORIZONTAL | Gravity.TOP);
+    }
+
+    /**
+     * @return the window token that's used by all ImmersiveModeConfirmation windows.
+     */
+    IBinder getWindowToken() {
+        return mWindowToken;
+    }
+
+    private class ClingWindowView extends FrameLayout {
+        private static final int BGCOLOR = 0x80000000;
+        private static final int OFFSET_DP = 96;
+        private static final int ANIMATION_DURATION = 250;
+
+        private final Runnable mConfirm;
+        private final ColorDrawable mColor = new ColorDrawable(0);
+        private final Interpolator mInterpolator;
+        private ValueAnimator mColorAnim;
+        private ViewGroup mClingLayout;
+
+        private Runnable mUpdateLayoutRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mClingLayout != null && mClingLayout.getParent() != null) {
+                    mClingLayout.setLayoutParams(getBubbleLayoutParams());
+                }
+            }
+        };
+
+        private ViewTreeObserver.OnComputeInternalInsetsListener mInsetsListener =
+                new ViewTreeObserver.OnComputeInternalInsetsListener() {
+                    private final int[] mTmpInt2 = new int[2];
+
+                    @Override
+                    public void onComputeInternalInsets(
+                            ViewTreeObserver.InternalInsetsInfo inoutInfo) {
+                        // Set touchable region to cover the cling layout.
+                        mClingLayout.getLocationInWindow(mTmpInt2);
+                        inoutInfo.setTouchableInsets(
+                                ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
+                        inoutInfo.touchableRegion.set(
+                                mTmpInt2[0],
+                                mTmpInt2[1],
+                                mTmpInt2[0] + mClingLayout.getWidth(),
+                                mTmpInt2[1] + mClingLayout.getHeight());
+                    }
+                };
+
+        private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
+                    post(mUpdateLayoutRunnable);
+                }
+            }
+        };
+
+        ClingWindowView(Context context, Runnable confirm) {
+            super(context);
+            mConfirm = confirm;
+            setBackground(mColor);
+            setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            mInterpolator = AnimationUtils
+                    .loadInterpolator(mContext, android.R.interpolator.linear_out_slow_in);
+        }
+
+        @Override
+        public void onAttachedToWindow() {
+            super.onAttachedToWindow();
+
+            DisplayMetrics metrics = new DisplayMetrics();
+            mWindowManager.getDefaultDisplay().getMetrics(metrics);
+            float density = metrics.density;
+
+            getViewTreeObserver().addOnComputeInternalInsetsListener(mInsetsListener);
+
+            // create the confirmation cling
+            mClingLayout = (ViewGroup)
+                    View.inflate(getContext(), R.layout.immersive_mode_cling, null);
+
+            final Button ok = mClingLayout.findViewById(R.id.ok);
+            ok.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    mConfirm.run();
+                }
+            });
+            addView(mClingLayout, getBubbleLayoutParams());
+
+            if (ActivityManager.isHighEndGfx()) {
+                final View cling = mClingLayout;
+                cling.setAlpha(0f);
+                cling.setTranslationY(-OFFSET_DP * density);
+
+                postOnAnimation(new Runnable() {
+                    @Override
+                    public void run() {
+                        cling.animate()
+                                .alpha(1f)
+                                .translationY(0)
+                                .setDuration(ANIMATION_DURATION)
+                                .setInterpolator(mInterpolator)
+                                .withLayer()
+                                .start();
+
+                        mColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(), 0, BGCOLOR);
+                        mColorAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+                            @Override
+                            public void onAnimationUpdate(ValueAnimator animation) {
+                                final int c = (Integer) animation.getAnimatedValue();
+                                mColor.setColor(c);
+                            }
+                        });
+                        mColorAnim.setDuration(ANIMATION_DURATION);
+                        mColorAnim.setInterpolator(mInterpolator);
+                        mColorAnim.start();
+                    }
+                });
+            } else {
+                mColor.setColor(BGCOLOR);
+            }
+
+            mContext.registerReceiver(mReceiver,
+                    new IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED));
+        }
+
+        @Override
+        public void onDetachedFromWindow() {
+            mContext.unregisterReceiver(mReceiver);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent motion) {
+            return true;
+        }
+    }
+
+    private void handleShow() {
+        if (DEBUG) Slog.d(TAG, "Showing immersive mode confirmation");
+
+        mClingWindow = new ClingWindowView(mContext, mConfirm);
+
+        // we will be hiding the nav bar, so layout as if it's already hidden
+        mClingWindow.setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+
+        // show the confirmation
+        WindowManager.LayoutParams lp = getClingWindowLayoutParams();
+        mWindowManager.addView(mClingWindow, lp);
+    }
+
+    private final Runnable mConfirm = new Runnable() {
+        @Override
+        public void run() {
+            if (DEBUG) Slog.d(TAG, "mConfirm.run()");
+            if (!sConfirmed) {
+                sConfirmed = true;
+                saveSetting(mContext);
+            }
+            handleHide();
+        }
+    };
+
+    private final class H extends Handler {
+        private static final int SHOW = 1;
+        private static final int HIDE = 2;
+
+        H(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch(msg.what) {
+                case SHOW:
+                    handleShow();
+                    break;
+                case HIDE:
+                    handleHide();
+                    break;
+            }
+        }
+    }
+
+    void onVrStateChangedLw(boolean enabled) {
+        mVrModeEnabled = enabled;
+        if (mVrModeEnabled) {
+            mHandler.removeMessages(H.SHOW);
+            mHandler.sendEmptyMessage(H.HIDE);
+        }
+    }
+
+    void onLockTaskModeChangedLw(int lockTaskState) {
+        mLockTaskState = lockTaskState;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/NavigationBarExperiments.java b/services/core/java/com/android/server/wm/NavigationBarExperiments.java
new file mode 100644
index 0000000..b74fb45
--- /dev/null
+++ b/services/core/java/com/android/server/wm/NavigationBarExperiments.java
@@ -0,0 +1,120 @@
+/*
+ * 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;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_BOTTOM;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_LEFT;
+import static android.view.WindowManagerPolicyConstants.NAV_BAR_RIGHT;
+
+import android.content.Context;
+import android.graphics.Rect;
+
+/**
+ * This class acts as a proxy for Navigation Bar experiments enabled with custom overlays
+ * {@see OverlayManagerService}. By default with no overlays, this class will essentially do nothing
+ * and pass the original resource data back. By default the navigation bar height/width is the same
+ * as the frame height/width and therefore any offsets calculated will cancel out and do nothing.
+ * TODO(b/113952590): Remove class once experiment in bug is completed
+ */
+public class NavigationBarExperiments {
+
+    private int mNavigationBarHeight;
+    private int mNavigationBarWidth;
+
+    /**
+     * This represents the height of the navigation bar buttons. With no experiments or overlays
+     * enabled, the frame height is the same as the normal navigation bar height.
+     */
+    private int mNavigationBarFrameHeight;
+
+    /**
+     * This represents the width of the navigation bar buttons. With no experiments or overlays
+     * enabled, the frame width is the same as the normal navigation bar width.
+     */
+    private int mNavigationBarFrameWidth;
+
+    /**
+     * Call when configuration change to refresh resource dimensions
+     * @param systemUiContext to get the resource values
+     */
+    public void onConfigurationChanged(Context systemUiContext) {
+        // Cache all the values again
+        mNavigationBarHeight = systemUiContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.navigation_bar_height);
+        mNavigationBarWidth = systemUiContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.navigation_bar_width);
+        mNavigationBarFrameHeight = systemUiContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.navigation_bar_frame_height);
+        mNavigationBarFrameWidth = systemUiContext.getResources().getDimensionPixelSize(
+                com.android.internal.R.dimen.navigation_bar_frame_width);
+    }
+
+    public int getNavigationBarHeight() {
+        return mNavigationBarHeight;
+    }
+
+    public int getNavigationBarWidth() {
+        return mNavigationBarWidth;
+    }
+
+    public int getNavigationBarFrameHeight() {
+        return mNavigationBarFrameHeight;
+    }
+
+    public int getNavigationBarFrameWidth() {
+        return mNavigationBarFrameWidth;
+    }
+
+    /**
+     * If navigation frame width/height is different than navigation bar width/height then only
+     * offset the ime's and home activity's window rects depending on the navigation bar position to
+     * add a gap where the navigation bar would have been drawn. With no experiments or overlays
+     * enabled, the height/width is the same as the frame height/width and the offsets calculated
+     * will be 0 and this function will do nothing.
+     * @param navPosition position of navigation bar (left, right or bottom)
+     * @param w the window that is being offset by experiment
+     */
+    public void offsetWindowFramesForNavBar(int navPosition, WindowState w) {
+        if (w.getAttrs().type != TYPE_INPUT_METHOD && w.getActivityType() != ACTIVITY_TYPE_HOME) {
+            return;
+        }
+
+        final WindowFrames windowFrames = w.getWindowFrames();
+        final Rect cf = windowFrames.mContentFrame;
+        switch (navPosition) {
+            case NAV_BAR_BOTTOM:
+                int navHeight = getNavigationBarFrameHeight() - getNavigationBarHeight();
+                if (navHeight > 0) {
+                    cf.bottom -= navHeight;
+                }
+                break;
+            case NAV_BAR_LEFT:
+            case NAV_BAR_RIGHT:
+                int navWidth = getNavigationBarFrameWidth() - getNavigationBarWidth();
+                if (navWidth > 0) {
+                    if (navPosition == NAV_BAR_LEFT) {
+                        cf.left += navWidth;
+                    } else {
+                        cf.right -= navWidth;
+                    }
+                }
+                break;
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index d21f67d..ba23258 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -514,8 +514,9 @@
      */
     private void getInsetBounds(Rect outRect) {
         synchronized (mService.mGlobalLock) {
-            mService.mPolicy.getStableInsetsLw(mDisplayInfo.rotation, mDisplayInfo.logicalWidth,
-                    mDisplayInfo.logicalHeight, mDisplayInfo.displayCutout, mTmpInsets);
+            mDisplayContent.getDisplayPolicy().getStableInsetsLw(mDisplayInfo.rotation,
+                    mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight,
+                    mDisplayInfo.displayCutout, mTmpInsets);
             outRect.set(mTmpInsets.left + mScreenEdgeInsets.x, mTmpInsets.top + mScreenEdgeInsets.y,
                     mDisplayInfo.logicalWidth - mTmpInsets.right - mScreenEdgeInsets.x,
                     mDisplayInfo.logicalHeight - mTmpInsets.bottom - mScreenEdgeInsets.y);
diff --git a/services/core/java/com/android/server/wm/PolicyControl.java b/services/core/java/com/android/server/wm/PolicyControl.java
new file mode 100644
index 0000000..4c8ce9e
--- /dev/null
+++ b/services/core/java/com/android/server/wm/PolicyControl.java
@@ -0,0 +1,262 @@
+/*
+ * 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;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.ArraySet;
+import android.util.Slog;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowManager.LayoutParams;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Runtime adjustments applied to the global window policy.
+ *
+ * This includes forcing immersive mode behavior for one or both system bars (based on a package
+ * list) and permanently disabling immersive mode confirmations for specific packages.
+ *
+ * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs.
+ * e.g.
+ *   to force immersive mode everywhere:
+ *     "immersive.full=*"
+ *   to force transient status for all apps except a specific package:
+ *     "immersive.status=apps,-com.package"
+ *   to disable the immersive mode confirmations for specific packages:
+ *     "immersive.preconfirms=com.package.one,com.package.two"
+ *
+ * Separate multiple name-value pairs with ':'
+ *   e.g. "immersive.status=apps:immersive.preconfirms=*"
+ */
+class PolicyControl {
+    private static final String TAG = "PolicyControl";
+    private static final boolean DEBUG = false;
+
+    private static final String NAME_IMMERSIVE_FULL = "immersive.full";
+    private static final String NAME_IMMERSIVE_STATUS = "immersive.status";
+    private static final String NAME_IMMERSIVE_NAVIGATION = "immersive.navigation";
+    private static final String NAME_IMMERSIVE_PRECONFIRMATIONS = "immersive.preconfirms";
+
+    private static String sSettingValue;
+    private static Filter sImmersivePreconfirmationsFilter;
+    private static Filter sImmersiveStatusFilter;
+    private static Filter sImmersiveNavigationFilter;
+
+    static int getSystemUiVisibility(WindowState win, LayoutParams attrs) {
+        attrs = attrs != null ? attrs : win.getAttrs();
+        int vis = win != null ? win.getSystemUiVisibility()
+                : (attrs.systemUiVisibility | attrs.subtreeSystemUiVisibility);
+        if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
+            vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                    | View.SYSTEM_UI_FLAG_FULLSCREEN
+                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+            vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.STATUS_BAR_TRANSLUCENT);
+        }
+        if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
+            vis |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
+                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
+            vis &= ~(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                    | View.NAVIGATION_BAR_TRANSLUCENT);
+        }
+        return vis;
+    }
+
+    static int getWindowFlags(WindowState win, LayoutParams attrs) {
+        attrs = attrs != null ? attrs : win.getAttrs();
+        int flags = attrs.flags;
+        if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
+            flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN;
+            flags &= ~(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
+                    | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+        }
+        if (sImmersiveNavigationFilter != null && sImmersiveNavigationFilter.matches(attrs)) {
+            flags &= ~WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
+        }
+        return flags;
+    }
+
+    static int adjustClearableFlags(WindowState win, int clearableFlags) {
+        final LayoutParams attrs = win != null ? win.getAttrs() : null;
+        if (sImmersiveStatusFilter != null && sImmersiveStatusFilter.matches(attrs)) {
+            clearableFlags &= ~View.SYSTEM_UI_FLAG_FULLSCREEN;
+        }
+        return clearableFlags;
+    }
+
+    static boolean disableImmersiveConfirmation(String pkg) {
+        return (sImmersivePreconfirmationsFilter != null
+                && sImmersivePreconfirmationsFilter.matches(pkg))
+                || ActivityManager.isRunningInTestHarness();
+    }
+
+    static boolean reloadFromSetting(Context context) {
+        if (DEBUG) Slog.d(TAG, "reloadFromSetting()");
+        String value = null;
+        try {
+            value = Settings.Global.getStringForUser(context.getContentResolver(),
+                    Settings.Global.POLICY_CONTROL,
+                    UserHandle.USER_CURRENT);
+            if (sSettingValue == value || sSettingValue != null && sSettingValue.equals(value)) {
+                return false;
+            }
+            setFilters(value);
+            sSettingValue = value;
+        } catch (Throwable t) {
+            Slog.w(TAG, "Error loading policy control, value=" + value, t);
+            return false;
+        }
+        return true;
+    }
+
+    static void dump(String prefix, PrintWriter pw) {
+        dump("sImmersiveStatusFilter", sImmersiveStatusFilter, prefix, pw);
+        dump("sImmersiveNavigationFilter", sImmersiveNavigationFilter, prefix, pw);
+        dump("sImmersivePreconfirmationsFilter", sImmersivePreconfirmationsFilter, prefix, pw);
+    }
+
+    private static void dump(String name, Filter filter, String prefix, PrintWriter pw) {
+        pw.print(prefix); pw.print("PolicyControl."); pw.print(name); pw.print('=');
+        if (filter == null) {
+            pw.println("null");
+        } else {
+            filter.dump(pw); pw.println();
+        }
+    }
+
+    private static void setFilters(String value) {
+        if (DEBUG) Slog.d(TAG, "setFilters: " + value);
+        sImmersiveStatusFilter = null;
+        sImmersiveNavigationFilter = null;
+        sImmersivePreconfirmationsFilter = null;
+        if (value != null) {
+            String[] nvps = value.split(":");
+            for (String nvp : nvps) {
+                int i = nvp.indexOf('=');
+                if (i == -1) continue;
+                String n = nvp.substring(0, i);
+                String v = nvp.substring(i + 1);
+                if (n.equals(NAME_IMMERSIVE_FULL)) {
+                    Filter f = Filter.parse(v);
+                    sImmersiveStatusFilter = sImmersiveNavigationFilter = f;
+                    if (sImmersivePreconfirmationsFilter == null) {
+                        sImmersivePreconfirmationsFilter = f;
+                    }
+                } else if (n.equals(NAME_IMMERSIVE_STATUS)) {
+                    Filter f = Filter.parse(v);
+                    sImmersiveStatusFilter = f;
+                } else if (n.equals(NAME_IMMERSIVE_NAVIGATION)) {
+                    Filter f = Filter.parse(v);
+                    sImmersiveNavigationFilter = f;
+                    if (sImmersivePreconfirmationsFilter == null) {
+                        sImmersivePreconfirmationsFilter = f;
+                    }
+                } else if (n.equals(NAME_IMMERSIVE_PRECONFIRMATIONS)) {
+                    Filter f = Filter.parse(v);
+                    sImmersivePreconfirmationsFilter = f;
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "immersiveStatusFilter: " + sImmersiveStatusFilter);
+            Slog.d(TAG, "immersiveNavigationFilter: " + sImmersiveNavigationFilter);
+            Slog.d(TAG, "immersivePreconfirmationsFilter: " + sImmersivePreconfirmationsFilter);
+        }
+    }
+
+    private static class Filter {
+        private static final String ALL = "*";
+        private static final String APPS = "apps";
+
+        private final ArraySet<String> mWhitelist;
+        private final ArraySet<String> mBlacklist;
+
+        private Filter(ArraySet<String> whitelist, ArraySet<String> blacklist) {
+            mWhitelist = whitelist;
+            mBlacklist = blacklist;
+        }
+
+        boolean matches(LayoutParams attrs) {
+            if (attrs == null) return false;
+            boolean isApp = attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
+                    && attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+            if (isApp && mBlacklist.contains(APPS)) return false;
+            if (onBlacklist(attrs.packageName)) return false;
+            if (isApp && mWhitelist.contains(APPS)) return true;
+            return onWhitelist(attrs.packageName);
+        }
+
+        boolean matches(String packageName) {
+            return !onBlacklist(packageName) && onWhitelist(packageName);
+        }
+
+        private boolean onBlacklist(String packageName) {
+            return mBlacklist.contains(packageName) || mBlacklist.contains(ALL);
+        }
+
+        private boolean onWhitelist(String packageName) {
+            return mWhitelist.contains(ALL) || mWhitelist.contains(packageName);
+        }
+
+        void dump(PrintWriter pw) {
+            pw.print("Filter[");
+            dump("whitelist", mWhitelist, pw); pw.print(',');
+            dump("blacklist", mBlacklist, pw); pw.print(']');
+        }
+
+        private void dump(String name, ArraySet<String> set, PrintWriter pw) {
+            pw.print(name); pw.print("=(");
+            final int n = set.size();
+            for (int i = 0; i < n; i++) {
+                if (i > 0) pw.print(',');
+                pw.print(set.valueAt(i));
+            }
+            pw.print(')');
+        }
+
+        @Override
+        public String toString() {
+            StringWriter sw = new StringWriter();
+            dump(new PrintWriter(sw, true));
+            return sw.toString();
+        }
+
+        // value = comma-delimited list of tokens, where token = (package name|apps|*)
+        // e.g. "com.package1", or "apps, com.android.keyguard" or "*"
+        static Filter parse(String value) {
+            if (value == null) return null;
+            ArraySet<String> whitelist = new ArraySet<String>();
+            ArraySet<String> blacklist = new ArraySet<String>();
+            for (String token : value.split(",")) {
+                token = token.trim();
+                if (token.startsWith("-") && token.length() > 1) {
+                    token = token.substring(1);
+                    blacklist.add(token);
+                } else {
+                    whitelist.add(token);
+                }
+            }
+            return new Filter(whitelist, blacklist);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 72e3562..b483fd3 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -120,13 +120,10 @@
     // events.
     int mTopFocusedDisplayId = INVALID_DISPLAY;
 
-    // Only a seperate transaction until we seperate the apply surface changes
+    // Only a separate transaction until we separate the apply surface changes
     // transaction from the global transaction.
     private final SurfaceControl.Transaction mDisplayTransaction = new SurfaceControl.Transaction();
 
-    private final Consumer<DisplayContent> mDisplayContentConfigChangesConsumer =
-            mService.mPolicy::onConfigurationChanged;
-
     private final Consumer<WindowState> mCloseSystemDialogsConsumer = w -> {
         if (w.mHasSurface) {
             try {
@@ -362,8 +359,6 @@
     public void onConfigurationChanged(Configuration newParentConfig) {
         prepareFreezingTaskBounds();
         super.onConfigurationChanged(newParentConfig);
-
-        forAllDisplays(mDisplayContentConfigChangesConsumer);
     }
 
     private void prepareFreezingTaskBounds() {
@@ -1052,6 +1047,12 @@
         }
     }
 
+    void forAllDisplayPolicies(Consumer<DisplayPolicy> callback) {
+        for (int i = mChildren.size() - 1; i >= 0; --i) {
+            callback.accept(mChildren.get(i).getDisplayPolicy());
+        }
+    }
+
     /**
      * Get current topmost focused IME window in system.
      * Will look on all displays in current Z-order.
diff --git a/services/core/java/com/android/server/wm/StackWindowController.java b/services/core/java/com/android/server/wm/StackWindowController.java
index baeedbc..35264a2 100644
--- a/services/core/java/com/android/server/wm/StackWindowController.java
+++ b/services/core/java/com/android/server/wm/StackWindowController.java
@@ -261,11 +261,12 @@
             final DisplayContent displayContent = stack.getDisplayContent();
             final DisplayInfo di = displayContent.getDisplayInfo();
             final DisplayCutout displayCutout = di.displayCutout;
+            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
 
             // Get the insets and display bounds
-            mService.mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+            displayPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
                     displayCutout, mTmpStableInsets);
-            mService.mPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+            displayPolicy.getNonDecorInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
                     displayCutout, mTmpNonDecorInsets);
             mTmpDisplayBounds.set(0, 0, di.logicalWidth, di.logicalHeight);
 
diff --git a/services/core/java/com/android/server/wm/StatusBarController.java b/services/core/java/com/android/server/wm/StatusBarController.java
new file mode 100644
index 0000000..b4de75b
--- /dev/null
+++ b/services/core/java/com/android/server/wm/StatusBarController.java
@@ -0,0 +1,113 @@
+/*
+ * 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;
+
+import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
+import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
+
+import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
+
+import android.app.StatusBarManager;
+import android.os.IBinder;
+import android.view.View;
+
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+/**
+ * Implements status bar specific behavior.
+ */
+public class StatusBarController extends BarController {
+
+    private final AppTransitionListener mAppTransitionListener = new AppTransitionListener() {
+
+        private Runnable mAppTransitionPending = () -> {
+            StatusBarManagerInternal statusBar = getStatusBarInternal();
+            if (statusBar != null && mWin != null) {
+                statusBar.appTransitionPending(mWin.getDisplayId());
+            }
+        };
+
+        private Runnable mAppTransitionCancelled = () -> {
+            StatusBarManagerInternal statusBar = getStatusBarInternal();
+            if (statusBar != null && mWin != null) {
+                statusBar.appTransitionCancelled(mWin.getDisplayId());
+            }
+        };
+
+        private Runnable mAppTransitionFinished = () -> {
+            StatusBarManagerInternal statusBar = getStatusBarInternal();
+            if (statusBar != null && mWin != null) {
+                statusBar.appTransitionFinished(mWin.getDisplayId());
+            }
+        };
+
+        @Override
+        public void onAppTransitionPendingLocked() {
+            mHandler.post(mAppTransitionPending);
+        }
+
+        @Override
+        public int onAppTransitionStartingLocked(int transit, IBinder openToken,
+                IBinder closeToken, long duration, long statusBarAnimationStartTime,
+                long statusBarAnimationDuration) {
+            mHandler.post(() -> {
+                StatusBarManagerInternal statusBar = getStatusBarInternal();
+                if (statusBar != null && mWin != null) {
+                    statusBar.appTransitionStarting(mWin.getDisplayId(),
+                            statusBarAnimationStartTime, statusBarAnimationDuration);
+                }
+            });
+            return 0;
+        }
+
+        @Override
+        public void onAppTransitionCancelledLocked(int transit) {
+            mHandler.post(mAppTransitionCancelled);
+        }
+
+        @Override
+        public void onAppTransitionFinishedLocked(IBinder token) {
+            mHandler.post(mAppTransitionFinished);
+        }
+    };
+
+    StatusBarController() {
+        super("StatusBar",
+                View.STATUS_BAR_TRANSIENT,
+                View.STATUS_BAR_UNHIDE,
+                View.STATUS_BAR_TRANSLUCENT,
+                StatusBarManager.WINDOW_STATUS_BAR,
+                FLAG_TRANSLUCENT_STATUS,
+                View.STATUS_BAR_TRANSPARENT);
+    }
+
+    void setTopAppHidesStatusBar(boolean hidesStatusBar) {
+        StatusBarManagerInternal statusBar = getStatusBarInternal();
+        if (statusBar != null) {
+            statusBar.setTopAppHidesStatusBar(hidesStatusBar);
+        }
+    }
+
+    @Override
+    protected boolean skipAnimation() {
+        return mWin.getAttrs().height == MATCH_PARENT;
+    }
+
+    AppTransitionListener getAppTransitionListener() {
+        return mAppTransitionListener;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
new file mode 100644
index 0000000..bdb76c2
--- /dev/null
+++ b/services/core/java/com/android/server/wm/SystemGesturesPointerEventListener.java
@@ -0,0 +1,291 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Slog;
+import android.view.GestureDetector;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
+import android.widget.OverScroller;
+
+/**
+ * Listens for system-wide input gestures, firing callbacks when detected.
+ * @hide
+ */
+class SystemGesturesPointerEventListener implements PointerEventListener {
+    private static final String TAG = "SystemGestures";
+    private static final boolean DEBUG = false;
+    private static final long SWIPE_TIMEOUT_MS = 500;
+    private static final int MAX_TRACKED_POINTERS = 32;  // max per input system
+    private static final int UNTRACKED_POINTER = -1;
+    private static final int MAX_FLING_TIME_MILLIS = 5000;
+
+    private static final int SWIPE_NONE = 0;
+    private static final int SWIPE_FROM_TOP = 1;
+    private static final int SWIPE_FROM_BOTTOM = 2;
+    private static final int SWIPE_FROM_RIGHT = 3;
+    private static final int SWIPE_FROM_LEFT = 4;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final int mSwipeStartThreshold;
+    private final int mSwipeDistanceThreshold;
+    private final Callbacks mCallbacks;
+    private final int[] mDownPointerId = new int[MAX_TRACKED_POINTERS];
+    private final float[] mDownX = new float[MAX_TRACKED_POINTERS];
+    private final float[] mDownY = new float[MAX_TRACKED_POINTERS];
+    private final long[] mDownTime = new long[MAX_TRACKED_POINTERS];
+
+    private GestureDetector mGestureDetector;
+
+    int screenHeight;
+    int screenWidth;
+    private int mDownPointers;
+    private boolean mSwipeFireable;
+    private boolean mDebugFireable;
+    private boolean mMouseHoveringAtEdge;
+    private long mLastFlingTime;
+
+    SystemGesturesPointerEventListener(Context context, Handler handler, Callbacks callbacks) {
+        mContext = context;
+        mHandler = handler;
+        mCallbacks = checkNull("callbacks", callbacks);
+        mSwipeStartThreshold = checkNull("context", context).getResources()
+                .getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
+        mSwipeDistanceThreshold = mSwipeStartThreshold;
+        if (DEBUG) Slog.d(TAG,  "mSwipeStartThreshold=" + mSwipeStartThreshold
+                + " mSwipeDistanceThreshold=" + mSwipeDistanceThreshold);
+    }
+
+    private static <T> T checkNull(String name, T arg) {
+        if (arg == null) {
+            throw new IllegalArgumentException(name + " must not be null");
+        }
+        return arg;
+    }
+
+    public void systemReady() {
+        mGestureDetector = new GestureDetector(mContext, new FlingGestureDetector(), mHandler);
+    }
+
+    @Override
+    public void onPointerEvent(MotionEvent event) {
+        if (mGestureDetector != null && event.isTouchEvent()) {
+            mGestureDetector.onTouchEvent(event);
+        }
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN:
+                mSwipeFireable = true;
+                mDebugFireable = true;
+                mDownPointers = 0;
+                captureDown(event, 0);
+                if (mMouseHoveringAtEdge) {
+                    mMouseHoveringAtEdge = false;
+                    mCallbacks.onMouseLeaveFromEdge();
+                }
+                mCallbacks.onDown();
+                break;
+            case MotionEvent.ACTION_POINTER_DOWN:
+                captureDown(event, event.getActionIndex());
+                if (mDebugFireable) {
+                    mDebugFireable = event.getPointerCount() < 5;
+                    if (!mDebugFireable) {
+                        if (DEBUG) Slog.d(TAG, "Firing debug");
+                        mCallbacks.onDebug();
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_MOVE:
+                if (mSwipeFireable) {
+                    final int swipe = detectSwipe(event);
+                    mSwipeFireable = swipe == SWIPE_NONE;
+                    if (swipe == SWIPE_FROM_TOP) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromTop");
+                        mCallbacks.onSwipeFromTop();
+                    } else if (swipe == SWIPE_FROM_BOTTOM) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromBottom");
+                        mCallbacks.onSwipeFromBottom();
+                    } else if (swipe == SWIPE_FROM_RIGHT) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromRight");
+                        mCallbacks.onSwipeFromRight();
+                    } else if (swipe == SWIPE_FROM_LEFT) {
+                        if (DEBUG) Slog.d(TAG, "Firing onSwipeFromLeft");
+                        mCallbacks.onSwipeFromLeft();
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_HOVER_MOVE:
+                if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+                    if (!mMouseHoveringAtEdge && event.getY() == 0) {
+                        mCallbacks.onMouseHoverAtTop();
+                        mMouseHoveringAtEdge = true;
+                    } else if (!mMouseHoveringAtEdge && event.getY() >= screenHeight - 1) {
+                        mCallbacks.onMouseHoverAtBottom();
+                        mMouseHoveringAtEdge = true;
+                    } else if (mMouseHoveringAtEdge
+                            && (event.getY() > 0 && event.getY() < screenHeight - 1)) {
+                        mCallbacks.onMouseLeaveFromEdge();
+                        mMouseHoveringAtEdge = false;
+                    }
+                }
+                break;
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL:
+                mSwipeFireable = false;
+                mDebugFireable = false;
+                mCallbacks.onUpOrCancel();
+                break;
+            default:
+                if (DEBUG) Slog.d(TAG, "Ignoring " + event);
+        }
+    }
+
+    private void captureDown(MotionEvent event, int pointerIndex) {
+        final int pointerId = event.getPointerId(pointerIndex);
+        final int i = findIndex(pointerId);
+        if (DEBUG) Slog.d(TAG, "pointer " + pointerId
+                + " down pointerIndex=" + pointerIndex + " trackingIndex=" + i);
+        if (i != UNTRACKED_POINTER) {
+            mDownX[i] = event.getX(pointerIndex);
+            mDownY[i] = event.getY(pointerIndex);
+            mDownTime[i] = event.getEventTime();
+            if (DEBUG) Slog.d(TAG, "pointer " + pointerId
+                    + " down x=" + mDownX[i] + " y=" + mDownY[i]);
+        }
+    }
+
+    private int findIndex(int pointerId) {
+        for (int i = 0; i < mDownPointers; i++) {
+            if (mDownPointerId[i] == pointerId) {
+                return i;
+            }
+        }
+        if (mDownPointers == MAX_TRACKED_POINTERS || pointerId == MotionEvent.INVALID_POINTER_ID) {
+            return UNTRACKED_POINTER;
+        }
+        mDownPointerId[mDownPointers++] = pointerId;
+        return mDownPointers - 1;
+    }
+
+    private int detectSwipe(MotionEvent move) {
+        final int historySize = move.getHistorySize();
+        final int pointerCount = move.getPointerCount();
+        for (int p = 0; p < pointerCount; p++) {
+            final int pointerId = move.getPointerId(p);
+            final int i = findIndex(pointerId);
+            if (i != UNTRACKED_POINTER) {
+                for (int h = 0; h < historySize; h++) {
+                    final long time = move.getHistoricalEventTime(h);
+                    final float x = move.getHistoricalX(p, h);
+                    final float y = move.getHistoricalY(p,  h);
+                    final int swipe = detectSwipe(i, time, x, y);
+                    if (swipe != SWIPE_NONE) {
+                        return swipe;
+                    }
+                }
+                final int swipe = detectSwipe(i, move.getEventTime(), move.getX(p), move.getY(p));
+                if (swipe != SWIPE_NONE) {
+                    return swipe;
+                }
+            }
+        }
+        return SWIPE_NONE;
+    }
+
+    private int detectSwipe(int i, long time, float x, float y) {
+        final float fromX = mDownX[i];
+        final float fromY = mDownY[i];
+        final long elapsed = time - mDownTime[i];
+        if (DEBUG) Slog.d(TAG, "pointer " + mDownPointerId[i]
+                + " moved (" + fromX + "->" + x + "," + fromY + "->" + y + ") in " + elapsed);
+        if (fromY <= mSwipeStartThreshold
+                && y > fromY + mSwipeDistanceThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_TOP;
+        }
+        if (fromY >= screenHeight - mSwipeStartThreshold
+                && y < fromY - mSwipeDistanceThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_BOTTOM;
+        }
+        if (fromX >= screenWidth - mSwipeStartThreshold
+                && x < fromX - mSwipeDistanceThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_RIGHT;
+        }
+        if (fromX <= mSwipeStartThreshold
+                && x > fromX + mSwipeDistanceThreshold
+                && elapsed < SWIPE_TIMEOUT_MS) {
+            return SWIPE_FROM_LEFT;
+        }
+        return SWIPE_NONE;
+    }
+
+    private final class FlingGestureDetector extends GestureDetector.SimpleOnGestureListener {
+
+        private OverScroller mOverscroller;
+
+        FlingGestureDetector() {
+            mOverscroller = new OverScroller(mContext);
+        }
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent e) {
+            if (!mOverscroller.isFinished()) {
+                mOverscroller.forceFinished(true);
+            }
+            return true;
+        }
+        @Override
+        public boolean onFling(MotionEvent down, MotionEvent up,
+                float velocityX, float velocityY) {
+            mOverscroller.computeScrollOffset();
+            long now = SystemClock.uptimeMillis();
+
+            if (mLastFlingTime != 0 && now > mLastFlingTime + MAX_FLING_TIME_MILLIS) {
+                mOverscroller.forceFinished(true);
+            }
+            mOverscroller.fling(0, 0, (int)velocityX, (int)velocityY,
+                    Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);
+            int duration = mOverscroller.getDuration();
+            if (duration > MAX_FLING_TIME_MILLIS) {
+                duration = MAX_FLING_TIME_MILLIS;
+            }
+            mLastFlingTime = now;
+            mCallbacks.onFling(duration);
+            return true;
+        }
+    }
+
+    interface Callbacks {
+        void onSwipeFromTop();
+        void onSwipeFromBottom();
+        void onSwipeFromRight();
+        void onSwipeFromLeft();
+        void onFling(int durationMs);
+        void onDown();
+        void onUpOrCancel();
+        void onMouseHoverAtTop();
+        void onMouseHoverAtBottom();
+        void onMouseLeaveFromEdge();
+        void onDebug();
+    }
+}
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 15de1ec..64f4ba5 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -521,7 +521,7 @@
         // Snap the position to a target.
         final int rotation = parentConfig.windowConfiguration.getRotation();
         final int orientation = parentConfig.orientation;
-        mService.mPolicy.getStableInsetsLw(rotation, displayWidth, displayHeight,
+        mDisplayContent.getDisplayPolicy().getStableInsetsLw(rotation, displayWidth, displayHeight,
                 displayCutout, outBounds);
         final DividerSnapAlgorithm algorithm = new DividerSnapAlgorithm(
                 mService.mContext.getResources(), displayWidth, displayHeight,
@@ -867,7 +867,8 @@
             // and its bounds can be adjusted after that. The bounds of all other stacks are
             // adjusted to occupy whatever screen space the docked stack isn't occupying.
             final DisplayCutout displayCutout = mDisplayContent.getDisplayInfo().displayCutout;
-            mService.mPolicy.getStableInsetsLw(parentConfig.windowConfiguration.getRotation(),
+            mDisplayContent.getDisplayPolicy().getStableInsetsLw(
+                    parentConfig.windowConfiguration.getRotation(),
                     displayRect.width(), displayRect.height(), displayCutout, mTmpRect2);
             final int position = new DividerSnapAlgorithm(mService.mContext.getResources(),
                     displayRect.width(),
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index b096bf2..e83b863 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -392,11 +392,6 @@
     public abstract boolean isStackVisible(int windowingMode);
 
     /**
-     * @return True if and only if the docked divider is currently in resize mode.
-     */
-    public abstract boolean isDockedDividerResizing();
-
-    /**
      * Requests the window manager to resend the windows for accessibility.
      */
     public abstract void computeWindowsForAccessibility();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8d5cd78..39a8465 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -171,6 +171,8 @@
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
+import android.service.vr.IVrManager;
+import android.service.vr.IVrStateCallbacks;
 import android.text.format.DateUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -231,6 +233,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastPrintWriter;
 import com.android.internal.util.LatencyTracker;
+import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.view.WindowManagerPolicyThread;
 import com.android.server.AnimationThread;
 import com.android.server.DisplayThread;
@@ -373,6 +376,18 @@
     boolean mKeyguardOrAodShowingOnDefaultDisplay;
     // VR Vr2d Display Id.
     int mVr2dDisplayId = INVALID_DISPLAY;
+    boolean mVrModeEnabled = false;
+
+    private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
+        @Override
+        public void onVrStateChanged(boolean enabled) {
+            synchronized (mGlobalLock) {
+                mVrModeEnabled = enabled;
+                mRoot.forAllDisplayPolicies(PooledLambda.obtainConsumer(
+                        DisplayPolicy::onVrStateChangedLw, PooledLambda.__(), enabled));
+            }
+        }
+    };
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
@@ -528,6 +543,7 @@
     boolean mForceDisplayEnabled = false;
     boolean mShowingBootMessages = false;
     boolean mBootAnimationStopped = false;
+    boolean mSystemReady = false;
 
     // Following variables are for debugging screen wakelock only.
     WindowState mLastWakeLockHoldingWindow = null;
@@ -579,9 +595,6 @@
     final WallpaperVisibilityListeners mWallpaperVisibilityListeners =
             new WallpaperVisibilityListeners();
 
-    int mSystemDecorLayer = 0;
-    final Rect mScreenRect = new Rect();
-
     boolean mDisplayFrozen = false;
     long mDisplayFreezeTime = 0;
     int mLastDisplayFreezeDuration = 0;
@@ -597,11 +610,6 @@
     boolean mClientFreezingScreen = false;
     int mAppsFreezingScreen = 0;
 
-    // Last systemUiVisibility we received from status bar.
-    int mLastStatusBarVisibility = 0;
-    // Last systemUiVisibility we dispatched to windows.
-    int mLastDispatchedSystemUiVisibility = 0;
-
     // State while inside of layoutAndPlaceSurfacesLocked().
     boolean mFocusMayChange;
 
@@ -649,6 +657,10 @@
                 Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE);
         private final Uri mAnimationDurationScaleUri =
                 Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE);
+        private final Uri mImmersiveModeConfirmationsUri =
+                Settings.Secure.getUriFor(Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS);
+        private final Uri mPolicyControlUri =
+                Settings.Global.getUriFor(Settings.Global.POLICY_CONTROL);
 
         public SettingsObserver() {
             super(new Handler());
@@ -661,6 +673,10 @@
                     UserHandle.USER_ALL);
             resolver.registerContentObserver(mAnimationDurationScaleUri, false, this,
                     UserHandle.USER_ALL);
+            resolver.registerContentObserver(mImmersiveModeConfirmationsUri, false, this,
+                    UserHandle.USER_ALL);
+            resolver.registerContentObserver(mPolicyControlUri, false, this,
+                    UserHandle.USER_ALL);
         }
 
         @Override
@@ -669,23 +685,40 @@
                 return;
             }
 
+            if (mImmersiveModeConfirmationsUri.equals(uri) || mPolicyControlUri.equals(uri)) {
+                updateSystemUiSettings();
+                return;
+            }
+
             if (mDisplayInversionEnabledUri.equals(uri)) {
                 updateCircularDisplayMaskIfNeeded();
+                return;
+            }
+
+            @UpdateAnimationScaleMode
+            final int mode;
+            if (mWindowAnimationScaleUri.equals(uri)) {
+                mode = WINDOW_ANIMATION_SCALE;
+            } else if (mTransitionAnimationScaleUri.equals(uri)) {
+                mode = TRANSITION_ANIMATION_SCALE;
+            } else if (mAnimationDurationScaleUri.equals(uri)) {
+                mode = ANIMATION_DURATION_SCALE;
             } else {
-                @UpdateAnimationScaleMode
-                final int mode;
-                if (mWindowAnimationScaleUri.equals(uri)) {
-                    mode = WINDOW_ANIMATION_SCALE;
-                } else if (mTransitionAnimationScaleUri.equals(uri)) {
-                    mode = TRANSITION_ANIMATION_SCALE;
-                } else if (mAnimationDurationScaleUri.equals(uri)) {
-                    mode = ANIMATION_DURATION_SCALE;
-                } else {
-                    // Ignoring unrecognized content changes
-                    return;
-                }
-                Message m = mH.obtainMessage(H.UPDATE_ANIMATION_SCALE, mode, 0);
-                mH.sendMessage(m);
+                // Ignoring unrecognized content changes
+                return;
+            }
+            Message m = mH.obtainMessage(H.UPDATE_ANIMATION_SCALE, mode, 0);
+            mH.sendMessage(m);
+        }
+
+        void updateSystemUiSettings() {
+            boolean changed;
+            synchronized (mWindowMap) {
+                changed = ImmersiveModeConfirmation.loadSetting(mCurrentUserId, mContext)
+                        || PolicyControl.reloadFromSetting(mContext);
+            }
+            if (changed) {
+                updateRotation(false /* alwaysSendConfiguration */, false /* forceRelayout */);
             }
         }
     }
@@ -1286,10 +1319,11 @@
             final boolean hasStatusBarServicePermission =
                     mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
                             == PackageManager.PERMISSION_GRANTED;
-            mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
+            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
+            displayPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
             win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
 
-            res = mPolicy.prepareAddWindowLw(win, attrs);
+            res = displayPolicy.prepareAddWindowLw(win, attrs);
             if (res != WindowManagerGlobal.ADD_OKAY) {
                 return res;
             }
@@ -1422,9 +1456,8 @@
                 taskBounds = null;
                 floatingStack = false;
             }
-            if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
-                    outFrame, outContentInsets, outStableInsets, outOutsets,
-                    outDisplayCutout)) {
+            if (displayPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
+                    outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
                 res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
             }
 
@@ -1842,6 +1875,8 @@
                 return 0;
             }
             displayId = win.getDisplayId();
+            final DisplayContent displayContent = win.getDisplayContent();
+            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
 
             WindowStateAnimator winAnimator = win.mWinAnimator;
             if (viewVisibility != View.GONE) {
@@ -1857,7 +1892,7 @@
             int attrChanges = 0;
             int flagChanges = 0;
             if (attrs != null) {
-                mPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);
+                displayPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);
                 // if they don't have the permission, mask out the status bar bits
                 if (seq == win.mSeq) {
                     int systemUiVisibility = attrs.systemUiVisibility
@@ -1996,7 +2031,7 @@
                 try {
                     result = createSurfaceControl(outSurface, result, win, winAnimator);
                 } catch (Exception e) {
-                    win.getDisplayContent().getInputMonitor().updateInputWindowsLw(true /*force*/);
+                    displayContent.getInputMonitor().updateInputWindowsLw(true /*force*/);
 
                     Slog.w(TAG_WM, "Exception thrown when creating surface for client "
                              + client + " (" + win.mAttrs.getTitle() + ")",
@@ -2007,7 +2042,6 @@
                 if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                     focusMayChange = true;
                 }
-                final DisplayContent displayContent = win.getDisplayContent();
                 if (win.mAttrs.type == TYPE_INPUT_METHOD
                         && displayContent.mInputMethodWindow == null) {
                     displayContent.setInputMethodWindowLocked(win);
@@ -2052,35 +2086,34 @@
             // updateFocusedWindowLocked() already assigned layers so we only need to
             // reassign them at this point if the IM window state gets shuffled
             boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
-            final DisplayContent dc = win.getDisplayContent();
             if (imMayMove) {
-                dc.computeImeTarget(true /* updateImeTarget */);
+                displayContent.computeImeTarget(true /* updateImeTarget */);
                 if (toBeDisplayed) {
                     // Little hack here -- we -should- be able to rely on the function to return
                     // true if the IME has moved and needs its layer recomputed. However, if the IME
                     // was hidden and isn't actually moved in the list, its layer may be out of data
                     // so we make sure to recompute it.
-                    dc.assignWindowLayers(false /* setLayoutNeeded */);
+                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
                 }
             }
 
             if (wallpaperMayMove) {
-                win.getDisplayContent().pendingLayoutChanges |=
+                displayContent.pendingLayoutChanges |=
                         WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
             }
 
             if (win.mAppToken != null) {
-                dc.mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
+                displayContent.mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
             }
 
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                     "relayoutWindow: updateOrientationFromAppTokens");
-            configChanged = dc.updateOrientationFromAppTokens();
+            configChanged = displayContent.updateOrientationFromAppTokens();
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
             if (toBeDisplayed && win.mIsWallpaper) {
-                DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
-                dc.mWallpaperController.updateWallpaperOffset(
+                DisplayInfo displayInfo = displayContent.getDisplayInfo();
+                displayContent.mWallpaperController.updateWallpaperOffset(
                         win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
             }
             if (win.mAppToken != null) {
@@ -2090,7 +2123,7 @@
                 winAnimator.mReportSurfaceResized = false;
                 result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
             }
-            if (mPolicy.isNavBarForcedShownLw(win)) {
+            if (displayPolicy.isNavBarForcedShownLw(win)) {
                 result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR;
             }
             if (!win.isGoneForLayoutLw()) {
@@ -2408,7 +2441,7 @@
                 mWaitingForConfig = true;
                 displayContent.setLayoutNeeded();
                 int anim[] = new int[2];
-                mPolicy.selectRotationAnimationLw(anim);
+                displayContent.getDisplayPolicy().selectRotationAnimationLw(anim);
 
                 startFreezingDisplayLocked(anim[0], anim[1], displayContent);
                 config = new Configuration(mTempConfiguration);
@@ -2603,7 +2636,9 @@
         }
     }
 
-    @Override
+    /**
+     * Notifies window manager that {@link DisplayPolicy#isShowingDreamLw} has changed.
+     */
     public void notifyShowingDreamChanged() {
         // TODO(multi-display): support show dream in multi-display.
         notifyKeyguardFlagsChanged(null /* callback */, DEFAULT_DISPLAY);
@@ -2634,6 +2669,21 @@
         mH.sendEmptyMessage(H.RECOMPUTE_FOCUS);
     }
 
+    @Override
+    public void onPowerKeyDown(boolean isScreenOn) {
+        mRoot.forAllDisplayPolicies(PooledLambda.obtainConsumer(
+                DisplayPolicy::onPowerKeyDown, PooledLambda.__(), isScreenOn));
+    }
+
+    @Override
+    public void onUserSwitched() {
+        mSettingsObserver.updateSystemUiSettings();
+        synchronized (mWindowMap) {
+            // force a re-application of focused window sysui visibility on each display.
+            mRoot.forAllDisplayPolicies(DisplayPolicy::resetSystemUiVisibilityLw);
+        }
+    }
+
     /**
      * Starts deferring layout passes. Useful when doing multiple changes but to optimize
      * performance, only one layout pass should be done. This can be called multiple times, and
@@ -2839,7 +2889,8 @@
 
     public boolean isShowingDream() {
         synchronized (mGlobalLock) {
-            return mPolicy.isShowingDreamLw();
+            // TODO: fix this when dream can be shown on non-default display.
+            return getDefaultDisplayContentLocked().getDisplayPolicy().isShowingDreamLw();
         }
     }
 
@@ -3672,13 +3723,6 @@
     }
 
     @Override
-    public WindowManagerPolicy.DisplayContentInfo getDefaultDisplayContentInfo() {
-        synchronized (mGlobalLock) {
-            return getDefaultDisplayContentLocked();
-        }
-    }
-
-    @Override
     public int getDefaultDisplayRotation() {
         synchronized (mGlobalLock) {
             return getDefaultDisplayContentLocked().getRotation();
@@ -4291,10 +4335,29 @@
     }
 
     public void systemReady() {
+        mSystemReady = true;
         mPolicy.systemReady();
+        mRoot.forAllDisplayPolicies(DisplayPolicy::systemReady);
         mTaskSnapshotController.systemReady();
         mHasWideColorGamutSupport = queryWideColorGamutSupport();
         mHasHdrSupport = queryHdrSupport();
+        UiThread.getHandler().post(mSettingsObserver::updateSystemUiSettings);
+        IVrManager vrManager = IVrManager.Stub.asInterface(
+                ServiceManager.getService(Context.VR_SERVICE));
+        if (vrManager != null) {
+            try {
+                final boolean vrModeEnabled = vrManager.getVrModeState();
+                synchronized (mGlobalLock) {
+                    vrManager.registerListener(mVrStateCallbacks);
+                    if (vrModeEnabled) {
+                        mVrModeEnabled = vrModeEnabled;
+                        mVrStateCallbacks.onVrStateChanged(vrModeEnabled);
+                    }
+                }
+            } catch (RemoteException e) {
+                // Ignore, we cannot do anything if we failed to register VR mode listener
+            }
+        }
     }
 
     private static boolean queryWideColorGamutSupport() {
@@ -5382,7 +5445,8 @@
             if (DEBUG_ORIENTATION) Slog.i(TAG_WM, "**** Dismissing screen rotation animation");
             DisplayInfo displayInfo = displayContent.getDisplayInfo();
             // Get rotation animation again, with new top window
-            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
+            if (!displayContent.getDisplayPolicy()
+                    .validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
                 mExitAnimId = mEnterAnimId = 0;
             }
             if (screenRotationAnimation.dismiss(mTransaction, MAX_ANIMATION_DURATION,
@@ -5522,7 +5586,7 @@
     }
 
     @Override
-    public void statusBarVisibilityChanged(int visibility) {
+    public void statusBarVisibilityChanged(int displayId, int visibility) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Caller does not hold permission "
@@ -5530,9 +5594,12 @@
         }
 
         synchronized (mGlobalLock) {
-            mLastStatusBarVisibility = visibility;
-            visibility = mPolicy.adjustSystemUiVisibilityLw(visibility);
-            updateStatusBarVisibilityLocked(visibility);
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent != null) {
+                displayContent.statusBarVisibilityChanged(visibility);
+            } else {
+                Slog.w(TAG, "statusBarVisibilityChanged with invalid displayId=" + displayId);
+            }
         }
     }
 
@@ -5548,49 +5615,36 @@
         }
     }
 
-    // TODO(multidisplay): StatusBar on multiple screens?
-    private boolean updateStatusBarVisibilityLocked(int visibility) {
-        if (mLastDispatchedSystemUiVisibility == visibility) {
-            return false;
-        }
-        final int globalDiff = (visibility ^ mLastDispatchedSystemUiVisibility)
-                // We are only interested in differences of one of the
-                // clearable flags...
-                & View.SYSTEM_UI_CLEARABLE_FLAGS
-                // ...if it has actually been cleared.
-                & ~visibility;
-
-        mLastDispatchedSystemUiVisibility = visibility;
-        mInputManager.setSystemUiVisibility(visibility);
-        getDefaultDisplayContentLocked().updateSystemUiVisibility(visibility, globalDiff);
-        return true;
-    }
-
+    // TODO: Make the callers use getNavBarPosition(int) only.
+    /**
+     * Used by SystemUI and shared SystemUI libraries.
+     * @see DisplayPolicy#getNavBarPosition()
+     */
     @Override
-    public void reevaluateStatusBarVisibility() {
-        synchronized (mGlobalLock) {
-            int visibility = mPolicy.adjustSystemUiVisibilityLw(mLastStatusBarVisibility);
-            if (updateStatusBarVisibilityLocked(visibility)) {
-                mWindowPlacerLocked.requestTraversal();
-            }
-        }
+    @WindowManagerPolicy.NavigationBarPosition
+    public int getNavBarPosition() {
+        return getNavBarPosition(Display.DEFAULT_DISPLAY);
     }
 
     /**
      * Used by ActivityManager to determine where to position an app with aspect ratio shorter then
      * the screen is.
-     * @see WindowManagerPolicy#getNavBarPosition()
+     * @see DisplayPolicy#getNavBarPosition()
      */
-    @Override
     @WindowManagerPolicy.NavigationBarPosition
-    public int getNavBarPosition() {
+    public int getNavBarPosition(int displayId) {
         synchronized (mGlobalLock) {
             // Perform layout if it was scheduled before to make sure that we get correct nav bar
             // position when doing rotations.
-            final DisplayContent defaultDisplayContent = getDefaultDisplayContentLocked();
-            defaultDisplayContent.performLayout(false /* initial */,
+            final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+            if (displayContent == null) {
+                Slog.w(TAG, "getNavBarPosition with invalid displayId=" + displayId
+                        + " callers=" + Debug.getCallers(3));
+                return -1;
+            }
+            displayContent.performLayout(false /* initial */,
                     false /* updateInputWindows */);
-            return mPolicy.getNavBarPosition();
+            return displayContent.getDisplayPolicy().getNavBarPosition();
         }
     }
 
@@ -5776,11 +5830,6 @@
         }
     }
 
-    @Override
-    public int getDockedDividerInsetsLw() {
-        return getDefaultDisplayContentLocked().getDockedDividerController().getContentInsets();
-    }
-
     private void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) {
         pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)");
         mPolicy.dump("    ", pw, args);
@@ -5969,12 +6018,6 @@
         mTaskSnapshotController.dump(pw, "  ");
 
         if (dumpAll) {
-            pw.print("  mSystemDecorLayer="); pw.print(mSystemDecorLayer);
-                    pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString());
-            if (mLastStatusBarVisibility != 0) {
-                pw.print("  mLastStatusBarVisibility=0x");
-                        pw.println(Integer.toHexString(mLastStatusBarVisibility));
-            }
             final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
             if (imeWindow != null) {
                 pw.print("  mInputMethodWindow="); pw.println(imeWindow);
@@ -6007,6 +6050,7 @@
                 pw.print("  mRecentsAnimationController="); pw.println(mRecentsAnimationController);
                 mRecentsAnimationController.dump(pw, "    ");
             }
+            PolicyControl.dump("  ", pw);
         }
     }
 
@@ -6270,7 +6314,7 @@
     public void onOverlayChanged() {
         synchronized (mGlobalLock) {
             mRoot.forAllDisplays(displayContent -> {
-                mPolicy.onOverlayChangedLw(displayContent);
+                displayContent.getDisplayPolicy().onOverlayChangedLw();
                 displayContent.updateDisplayInfo();
             });
             requestTraversal();
@@ -6500,7 +6544,7 @@
         final DisplayContent dc = mRoot.getDisplayContent(displayId);
         if (dc != null) {
             final DisplayInfo di = dc.getDisplayInfo();
-            mPolicy.getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
+            dc.getDisplayPolicy().getStableInsetsLw(di.rotation, di.logicalWidth, di.logicalHeight,
                     di.displayCutout, outInsets);
         }
     }
@@ -7102,13 +7146,6 @@
         }
 
         @Override
-        public boolean isDockedDividerResizing() {
-            synchronized (mGlobalLock) {
-                return getDefaultDisplayContentLocked().getDockedDividerController().isResizing();
-            }
-        }
-
-        @Override
         public void computeWindowsForAccessibility() {
             final AccessibilityController accessibilityController;
             synchronized (mGlobalLock) {
@@ -7367,9 +7404,11 @@
      *                      {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
      *                      {@link ActivityManager#LOCK_TASK_MODE_PINNED}.
      */
-    public void onLockTaskStateChanged(int lockTaskState) {
+    void onLockTaskStateChanged(int lockTaskState) {
+        // TODO: pass in displayId to determine which display the lock task state changed
         synchronized (mGlobalLock) {
-            mPolicy.onLockTaskStateChangedLw(lockTaskState);
+            mRoot.forAllDisplayPolicies(PooledLambda.obtainConsumer(
+                    DisplayPolicy::onLockTaskStateChangedLw, PooledLambda.__(), lockTaskState));
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index a117cf3..567b583 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -179,6 +179,7 @@
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
+import android.view.InputWindowHandle;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
@@ -191,7 +192,6 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
-import android.view.InputWindowHandle;
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.LocalAnimationAdapter.AnimationSpec;
 import com.android.server.wm.utils.InsetUtils;
@@ -1800,7 +1800,7 @@
             // also been registered in display.
             dc.mTapExcludeProvidingWindows.remove(this);
         }
-        mPolicy.removeWindowLw(this);
+        dc.getDisplayPolicy().removeWindowLw(this);
 
         disposeInputChannel();
 
@@ -2982,7 +2982,7 @@
 
         mClient.resized(frame, overscanInsets, contentInsets, visibleInsets, stableInsets, outsets,
                 reportDraw, mergedConfiguration, getBackdropFrame(frame), forceRelayout,
-                mPolicy.isNavBarForcedShownLw(this), displayId,
+                getDisplayContent().getDisplayPolicy().isNavBarForcedShownLw(this), displayId,
                 new DisplayCutout.ParcelableWrapper(displayCutout));
         mDragResizingChangeReported = true;
     }
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 15ea5b5..e090cc5 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1343,7 +1343,7 @@
         // is running.
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "WSA#applyAnimationLocked");
         if (mWin.mToken.okToAnimate()) {
-            int anim = mPolicy.selectAnimationLw(mWin, transit);
+            int anim = mWin.getDisplayContent().getDisplayPolicy().selectAnimationLw(mWin, transit);
             int attr = -1;
             Animation a = null;
             if (anim != 0) {