Merge changes from topic "ccassidy-sbwsc"

* changes:
  [Status Bar Refactor] Have SBHideIconsForBouncerManager listen to window state directly instead of getting it from StatusBar.java
  [Status Bar Refactor] Have NotificationShadeWindowViewController query the window state directly instead of going through StatusBar.java.
  [Status Bar Refactor] Move status bar window state tracking into a centralized class and add some listeners for it.
  [Status Bar Refactor] Pass status bar view's controller to NotificationShadeWindowViewController instead of the view itself.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 9ca904b..03b1627 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.view.View.GONE;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
@@ -197,6 +198,7 @@
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.Utils;
 import com.android.systemui.util.settings.SecureSettings;
@@ -732,7 +734,9 @@
             NotificationEntryManager notificationEntryManager,
             CommunalStateController communalStateController,
             KeyguardStateController keyguardStateController,
-            StatusBarStateController statusBarStateController, DozeLog dozeLog,
+            StatusBarStateController statusBarStateController,
+            StatusBarWindowStateController statusBarWindowStateController,
+            DozeLog dozeLog,
             DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
             LatencyTracker latencyTracker, PowerManager powerManager,
             AccessibilityManager accessibilityManager, @DisplayId int displayId,
@@ -862,6 +866,7 @@
                 mQs.animateHeaderSlidingOut();
             }
         });
+        statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
         mThemeResId = mView.getContext().getThemeResId();
         mKeyguardBypassController = bypassController;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -5009,4 +5014,14 @@
     public PhoneStatusBarView.TouchEventHandler getStatusBarTouchEventHandler() {
         return mStatusBarViewTouchEventHandler;
     }
+
+    private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) {
+        if (state != WINDOW_STATE_SHOWING
+                && mStatusBarStateController.getState() == StatusBarState.SHADE) {
+            collapsePanel(
+                    false /* animate */,
+                    false /* delayed */,
+                    1.0f /* speedUpFactor */);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
index a6980a4..4e2eb6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
@@ -16,10 +16,7 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
 import android.app.StatusBarManager;
-import android.graphics.RectF;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.media.AudioManager;
 import android.media.session.MediaSessionLegacyHelper;
@@ -47,6 +44,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.tuner.TunerService;
 
 import java.io.FileDescriptor;
@@ -68,6 +66,7 @@
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final LockIconViewController mLockIconViewController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    private final StatusBarWindowStateController mStatusBarWindowStateController;
 
     private GestureDetector mGestureDetector;
     private View mBrightnessMirror;
@@ -75,7 +74,7 @@
     private boolean mTouchCancelled;
     private boolean mExpandAnimationRunning;
     private NotificationStackScrollLayout mStackScrollLayout;
-    private PhoneStatusBarView mStatusBarView;
+    private PhoneStatusBarViewController mStatusBarViewController;
     private StatusBar mService;
     private NotificationShadeWindowController mNotificationShadeWindowController;
     private DragDownHelper mDragDownHelper;
@@ -86,8 +85,6 @@
     private final NotificationPanelViewController mNotificationPanelViewController;
     private final PanelExpansionStateManager mPanelExpansionStateManager;
 
-    // Used for determining view / touch intersection
-    private final RectF mTempRect = new RectF();
     private boolean mIsTrackingBarGesture = false;
 
     @Inject
@@ -103,6 +100,7 @@
             PanelExpansionStateManager panelExpansionStateManager,
             NotificationStackScrollLayoutController notificationStackScrollLayoutController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
+            StatusBarWindowStateController statusBarWindowStateController,
             LockIconViewController lockIconViewController) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
@@ -115,6 +113,7 @@
         mDepthController = depthController;
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
+        mStatusBarWindowStateController = statusBarWindowStateController;
         mLockIconViewController = lockIconViewController;
 
         // This view is not part of the newly inflated expanded status bar.
@@ -175,7 +174,7 @@
         mView.setInteractionEventHandler(new NotificationShadeWindowView.InteractionEventHandler() {
             @Override
             public Boolean handleDispatchTouchEvent(MotionEvent ev) {
-                if (mStatusBarView == null) {
+                if (mStatusBarViewController == null) { // Fix for b/192490822
                     Log.w(TAG, "Ignoring touch while statusBarView not yet set.");
                     return false;
                 }
@@ -243,27 +242,27 @@
                     expandingBelowNotch = true;
                 }
                 if (expandingBelowNotch) {
-                    return mStatusBarView.dispatchTouchEvent(ev);
+                    return mStatusBarViewController.sendTouchToView(ev);
                 }
 
                 if (!mIsTrackingBarGesture && isDown
                         && mNotificationPanelViewController.isFullyCollapsed()) {
                     float x = ev.getRawX();
                     float y = ev.getRawY();
-                    if (isIntersecting(mStatusBarView, x, y)) {
-                        if (mService.isSameStatusBarState(WINDOW_STATE_SHOWING)) {
+                    if (mStatusBarViewController.touchIsWithinView(x, y)) {
+                        if (mStatusBarWindowStateController.windowIsShowing()) {
                             mIsTrackingBarGesture = true;
-                            return mStatusBarView.dispatchTouchEvent(ev);
+                            return mStatusBarViewController.sendTouchToView(ev);
                         } else { // it's hidden or hiding, don't send to notification shade.
                             return true;
                         }
                     }
                 } else if (mIsTrackingBarGesture) {
-                    final boolean sendToNotification = mStatusBarView.dispatchTouchEvent(ev);
+                    final boolean sendToStatusBar = mStatusBarViewController.sendTouchToView(ev);
                     if (isUp || isCancel) {
                         mIsTrackingBarGesture = false;
                     }
-                    return sendToNotification;
+                    return sendToStatusBar;
                 }
 
                 return null;
@@ -442,8 +441,8 @@
         }
     }
 
-    public void setStatusBarView(PhoneStatusBarView statusBarView) {
-        mStatusBarView = statusBarView;
+    public void setStatusBarViewController(PhoneStatusBarViewController statusBarViewController) {
+        mStatusBarViewController = statusBarViewController;
     }
 
     public void setService(StatusBar statusBar, NotificationShadeWindowController controller) {
@@ -455,11 +454,4 @@
     void setDragDownHelper(DragDownHelper dragDownHelper) {
         mDragDownHelper = dragDownHelper;
     }
-
-    private boolean isIntersecting(View view, float x, float y) {
-        int[] mTempLocation = view.getLocationOnScreen();
-        mTempRect.set(mTempLocation[0], mTempLocation[1], mTempLocation[0] + view.getWidth(),
-                mTempLocation[1] + view.getHeight());
-        return mTempRect.contains(x, y);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index b9386bd..1cb19ab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -17,6 +17,7 @@
 
 import android.content.res.Configuration
 import android.graphics.Point
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
@@ -92,6 +93,30 @@
         mView.importantForAccessibility = mode
     }
 
+    /**
+     * Sends a touch event to the status bar view.
+     *
+     * This is required in certain cases because the status bar view is in a separate window from
+     * the rest of SystemUI, and other windows may decide that their touch should instead be treated
+     * as a status bar window touch.
+     */
+    fun sendTouchToView(ev: MotionEvent): Boolean {
+        return mView.dispatchTouchEvent(ev)
+    }
+
+    /**
+     * Returns true if the given (x, y) point (in screen coordinates) is within the status bar
+     * view's range and false otherwise.
+     */
+    fun touchIsWithinView(x: Float, y: Float): Boolean {
+        val left = mView.locationOnScreen[0]
+        val top = mView.locationOnScreen[1]
+        return left <= x &&
+                x <= left + mView.width &&
+                top <= y &&
+                y <= top + mView.height
+    }
+
     class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
         override fun getViewCenter(view: View, outPoint: Point) =
             when (view.id) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index 6f8a334..3671bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -233,6 +233,7 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -338,15 +339,9 @@
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private StatusBarCommandQueueCallbacks mCommandQueueCallbacks;
 
-    void setWindowState(int state) {
-        mStatusBarWindowState =  state;
-        mStatusBarWindowHidden = state == WINDOW_STATE_HIDDEN;
-        mStatusBarHideIconsForBouncerManager.setStatusBarWindowHidden(mStatusBarWindowHidden);
-        if (mStatusBarView != null) {
-            // Should #updateHideIconsForBouncer always be called, regardless of whether we have a
-            //   status bar view? If so, we can make #updateHideIconsForBouncer private.
-            mStatusBarHideIconsForBouncerManager.updateHideIconsForBouncer(/* animate= */ false);
-        }
+    void onStatusBarWindowStateChanged(@WindowVisibleState int state) {
+        updateBubblesVisibility();
+        mStatusBarWindowState = state;
     }
 
     void acquireGestureWakeLock(long time) {
@@ -465,7 +460,7 @@
     private PhoneStatusBarViewController mPhoneStatusBarViewController;
     private PhoneStatusBarTransitions mStatusBarTransitions;
     private AuthRippleController mAuthRippleController;
-    private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
+    @WindowVisibleState private int mStatusBarWindowState = WINDOW_STATE_SHOWING;
     protected NotificationShadeWindowController mNotificationShadeWindowController;
     private final StatusBarWindowController mStatusBarWindowController;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@@ -695,6 +690,7 @@
             LightBarController lightBarController,
             AutoHideController autoHideController,
             StatusBarWindowController statusBarWindowController,
+            StatusBarWindowStateController statusBarWindowStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             StatusBarSignalPolicy statusBarSignalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
@@ -877,6 +873,7 @@
         mStartingSurfaceOptional = startingSurfaceOptional;
         mNotifPipelineFlags = notifPipelineFlags;
         lockscreenShadeTransitionController.setStatusbar(this);
+        statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
 
         mScreenOffAnimationController = screenOffAnimationController;
 
@@ -1138,7 +1135,8 @@
                     mStatusBarView = statusBarView;
                     mPhoneStatusBarViewController = statusBarViewController;
                     mStatusBarTransitions = statusBarTransitions;
-                    mNotificationShadeWindowViewController.setStatusBarView(mStatusBarView);
+                    mNotificationShadeWindowViewController
+                            .setStatusBarViewController(mPhoneStatusBarViewController);
                     // Ensure we re-propagate panel expansion values to the panel controller and
                     // any listeners it may have, such as PanelBar. This will also ensure we
                     // re-display the notification panel if necessary (for example, if
@@ -2111,10 +2109,6 @@
         }
     }
 
-    boolean isSameStatusBarState(int state) {
-        return mStatusBarWindowState == state;
-    }
-
     public GestureRecorder getGestureRecorder() {
         return mGestureRec;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
index b391de3..51f2e67 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacks.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-import static android.app.StatusBarManager.windowStateToString;
 import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.InsetsState.containsType;
 
@@ -58,7 +56,6 @@
 import com.android.systemui.qs.QSPanelController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DisableFlagsLogger;
-import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
@@ -515,30 +512,6 @@
     }
 
     @Override
-    public void setWindowState(
-            int displayId, @StatusBarManager.WindowType int window,
-            @StatusBarManager.WindowVisibleState int state) {
-        if (displayId != mDisplayId) {
-            return;
-        }
-        boolean showing = state == WINDOW_STATE_SHOWING;
-        if (mNotificationShadeWindowView != null
-                && window == StatusBarManager.WINDOW_STATUS_BAR
-                && !mStatusBar.isSameStatusBarState(state)) {
-            mStatusBar.setWindowState(state);
-            if (StatusBar.DEBUG_WINDOW_STATE) {
-                Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state));
-            }
-            if (!showing && mStatusBarStateController.getState() == StatusBarState.SHADE) {
-                mNotificationPanelViewController.collapsePanel(
-                            false /* animate */, false /* delayed */, 1.0f /* speedUpFactor */);
-            }
-        }
-
-        mStatusBar.updateBubblesVisibility();
-    }
-
-    @Override
     public void showAssistDisclosure() {
         mAssistManager.showDisclosure();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
index d2181d0..17516e0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt
@@ -1,10 +1,12 @@
 package com.android.systemui.statusbar.phone
 
+import android.app.StatusBarManager
 import com.android.systemui.Dumpable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import java.io.FileDescriptor
 import java.io.PrintWriter
@@ -25,6 +27,7 @@
 class StatusBarHideIconsForBouncerManager @Inject constructor(
     private val commandQueue: CommandQueue,
     @Main private val mainExecutor: DelayableExecutor,
+    statusBarWindowStateController: StatusBarWindowStateController,
     dumpManager: DumpManager
 ) : Dumpable {
     // State variables set by external classes.
@@ -42,6 +45,9 @@
 
     init {
         dumpManager.registerDumpable(this)
+        statusBarWindowStateController.addListener {
+                state -> setStatusBarStateAndTriggerUpdate(state)
+        }
     }
 
     /** Returns true if the status bar icons should be hidden in the bouncer. */
@@ -49,8 +55,9 @@
         return hideIconsForBouncer || wereIconsJustHidden
     }
 
-    fun setStatusBarWindowHidden(statusBarWindowHidden: Boolean) {
-        this.statusBarWindowHidden = statusBarWindowHidden
+    private fun setStatusBarStateAndTriggerUpdate(@StatusBarManager.WindowVisibleState state: Int) {
+        statusBarWindowHidden = state == StatusBarManager.WINDOW_STATE_HIDDEN
+        updateHideIconsForBouncer(animate = false)
     }
 
     fun setDisplayId(displayId: Int) {
@@ -87,7 +94,7 @@
      * Updates whether the status bar icons should be hidden in the bouncer. May trigger
      * [commandQueue.recomputeDisableFlags] if the icon visibility status changes.
      */
-    fun updateHideIconsForBouncer(animate: Boolean) {
+    private fun updateHideIconsForBouncer(animate: Boolean) {
         val hideBecauseApp =
             topAppHidesStatusBar &&
                     isOccluded &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index c6ef4b5..50176fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -106,6 +106,7 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.MessageRouter;
@@ -141,6 +142,7 @@
             LightBarController lightBarController,
             AutoHideController autoHideController,
             StatusBarWindowController statusBarWindowController,
+            StatusBarWindowStateController statusBarWindowStateController,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             StatusBarSignalPolicy statusBarSignalPolicy,
             PulseExpansionHandler pulseExpansionHandler,
@@ -237,6 +239,7 @@
                 lightBarController,
                 autoHideController,
                 statusBarWindowController,
+                statusBarWindowStateController,
                 keyguardUpdateMonitor,
                 statusBarSignalPolicy,
                 pulseExpansionHandler,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
new file mode 100644
index 0000000..3a14914
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2021 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.systemui.statusbar.window
+
+import android.app.StatusBarManager
+import android.app.StatusBarManager.WindowVisibleState
+import android.app.StatusBarManager.WINDOW_STATE_SHOWING
+import android.app.StatusBarManager.WINDOW_STATUS_BAR
+import android.app.StatusBarManager.windowStateToString
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.DisplayId
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.phone.StatusBar
+import javax.inject.Inject
+
+/**
+ * A centralized class maintaining the state of the status bar window.
+ *
+ * Classes that want to get updates about the status bar window state should subscribe to this class
+ * via [addListener] and should NOT add their own callback on [CommandQueue].
+ */
+@SysUISingleton
+class StatusBarWindowStateController @Inject constructor(
+    @DisplayId private val thisDisplayId: Int,
+    commandQueue: CommandQueue
+) {
+    private val commandQueueCallback = object : CommandQueue.Callbacks {
+        override fun setWindowState(
+            displayId: Int,
+            @StatusBarManager.WindowType window: Int,
+            @WindowVisibleState state: Int
+        ) {
+            this@StatusBarWindowStateController.setWindowState(displayId, window, state)
+        }
+    }
+    private val listeners: MutableSet<StatusBarWindowStateListener> = HashSet()
+
+    @WindowVisibleState private var windowState: Int = WINDOW_STATE_SHOWING
+
+    init {
+        commandQueue.addCallback(commandQueueCallback)
+    }
+
+    /** Adds a listener. */
+    fun addListener(listener: StatusBarWindowStateListener) {
+        listeners.add(listener)
+    }
+
+    /** Returns true if the window is currently showing. */
+    fun windowIsShowing() = windowState == WINDOW_STATE_SHOWING
+
+    private fun setWindowState(
+        displayId: Int,
+        @StatusBarManager.WindowType window: Int,
+        @WindowVisibleState state: Int
+    ) {
+        if (displayId != thisDisplayId) {
+            return
+        }
+        if (window != WINDOW_STATUS_BAR) {
+            return
+        }
+        if (windowState == state) {
+            return
+        }
+
+        windowState = state
+        if (StatusBar.DEBUG_WINDOW_STATE) {
+            Log.d(StatusBar.TAG, "Status bar " + windowStateToString(state))
+        }
+        listeners.forEach { it.onStatusBarWindowStateChanged(state) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateListener.kt
new file mode 100644
index 0000000..f3bab04
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateListener.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2021 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.systemui.statusbar.window
+
+import android.app.StatusBarManager
+
+/** Listener interface for changes in the status bar window state. */
+fun interface StatusBarWindowStateListener {
+    fun onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState state: Int)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
index 3b72f00..5ada6d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
@@ -141,6 +141,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherController;
 import com.android.systemui.statusbar.policy.KeyguardUserSwitcherView;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.settings.SecureSettings;
@@ -359,6 +360,8 @@
     private NotificationsQSContainerController mNotificationsQSContainerController;
     @Mock
     private QsFrameTranslateController mQsFrameTranslateController;
+    @Mock
+    private StatusBarWindowStateController mStatusBarWindowStateController;
     private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -495,7 +498,9 @@
                 coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
                 mFalsingManager, new FalsingCollectorFake(),
                 mNotificationLockscreenUserManager, mNotificationEntryManager,
-                mCommunalStateController, mKeyguardStateController, mStatusBarStateController,
+                mCommunalStateController, mKeyguardStateController,
+                mStatusBarStateController,
+                mStatusBarWindowStateController,
                 mDozeLog, mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mCommunalSourceMonitor, mMetricsLogger, mActivityManager, mConfigurationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt
new file mode 100644
index 0000000..12e71af
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright (C) 2021 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.systemui.statusbar.phone
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.dock.DockManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationShadeDepthController
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.tuner.TunerService
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
+    private lateinit var mController: NotificationShadeWindowViewController
+
+    @Mock
+    private lateinit var mView: NotificationShadeWindowView
+    @Mock
+    private lateinit var mTunerService: TunerService
+    @Mock
+    private lateinit var mStatusBarStateController: SysuiStatusBarStateController
+    @Mock
+    private lateinit var mStatusBar: StatusBar
+    @Mock
+    private lateinit var mDockManager: DockManager
+    @Mock
+    private lateinit var mNotificationPanelViewController: NotificationPanelViewController
+    @Mock
+    private lateinit var mNotificationShadeDepthController: NotificationShadeDepthController
+    @Mock
+    private lateinit var mNotificationShadeWindowController: NotificationShadeWindowController
+    @Mock
+    private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
+    @Mock
+    private lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock
+    private lateinit var mStatusBarWindowStateController: StatusBarWindowStateController
+    @Mock
+    private lateinit var mLockscreenShadeTransitionController: LockscreenShadeTransitionController
+    @Mock
+    private lateinit var mLockIconViewController: LockIconViewController
+    @Mock
+    private lateinit var mPhoneStatusBarViewController: PhoneStatusBarViewController
+
+    private lateinit var mInteractionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+    private lateinit var mInteractionEventHandler: InteractionEventHandler
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(mView.bottom).thenReturn(VIEW_BOTTOM)
+
+        mController = NotificationShadeWindowViewController(
+            mLockscreenShadeTransitionController,
+            FalsingCollectorFake(),
+            mTunerService,
+            mStatusBarStateController,
+            mDockManager,
+            mNotificationShadeDepthController,
+            mView,
+            mNotificationPanelViewController,
+            PanelExpansionStateManager(),
+            stackScrollLayoutController,
+            mStatusBarKeyguardViewManager,
+            mStatusBarWindowStateController,
+            mLockIconViewController
+        )
+        mController.setupExpandedStatusBar()
+        mController.setService(mStatusBar, mNotificationShadeWindowController)
+
+        mInteractionEventHandlerCaptor =
+            ArgumentCaptor.forClass(InteractionEventHandler::class.java)
+        verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture())
+            mInteractionEventHandler = mInteractionEventHandlerCaptor.value
+    }
+
+    // Note: So far, these tests only cover interactions with the status bar view controller. More
+    // tests need to be added to test the rest of handleDispatchTouchEvent.
+
+    @Test
+    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
+        mController.setStatusBarViewController(null)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
+
+        assertThat(returnVal).isFalse()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+        whenever(mPhoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(ev)
+
+        verify(mPhoneStatusBarViewController).sendTouchToView(ev)
+        assertThat(returnVal).isTrue()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        val downEvBelow = MotionEvent.obtain(
+            0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0
+        )
+        mInteractionEventHandler.handleDispatchTouchEvent(downEvBelow)
+
+        val nextEvent = MotionEvent.obtain(
+            0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0
+        )
+        whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent)
+
+        verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent)
+        assertThat(returnVal).isTrue()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
+        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
+        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+            .thenReturn(true)
+        whenever(mPhoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
+
+        verify(mPhoneStatusBarViewController).sendTouchToView(downEv)
+        assertThat(returnVal).isTrue()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
+        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+            .thenReturn(true)
+        // Item we're testing
+        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(false)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
+
+        verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
+        assertThat(returnVal).isNull()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
+        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
+        // Item we're testing
+        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+            .thenReturn(false)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
+
+        verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
+        assertThat(returnVal).isNull()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
+        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+            .thenReturn(true)
+        // Item we're testing
+        whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(false)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
+
+        verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
+        assertThat(returnVal).isTrue()
+    }
+
+    @Test
+    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
+        mController.setStatusBarViewController(mPhoneStatusBarViewController)
+        whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
+        whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
+        whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+            .thenReturn(true)
+
+        // Down event first
+        mInteractionEventHandler.handleDispatchTouchEvent(downEv)
+
+        // Then another event
+        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+        whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+
+        val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent)
+
+        verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent)
+        assertThat(returnVal).isTrue()
+    }
+}
+
+private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
index aaf051c..d885da8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.tuner.TunerService;
 
 import org.junit.Before;
@@ -75,6 +76,7 @@
     @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
     @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     @Mock private LockIconViewController mLockIconViewController;
 
@@ -107,6 +109,7 @@
                 new PanelExpansionStateManager(),
                 mNotificationStackScrollLayoutController,
                 mStatusBarKeyguardViewManager,
+                mStatusBarWindowStateController,
                 mLockIconViewController);
         mController.setupExpandedStatusBar();
         mController.setService(mStatusBar, mNotificationShadeWindowController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 235de1e..c65a6b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -77,9 +77,10 @@
             val parent = FrameLayout(mContext) // add parent to keep layout params
             view = LayoutInflater.from(mContext)
                 .inflate(R.layout.status_bar, parent, false) as PhoneStatusBarView
+            view.setLeftTopRightBottom(VIEW_LEFT, VIEW_TOP, VIEW_RIGHT, VIEW_BOTTOM)
         }
 
-        controller = createController(view)
+        controller = createAndInitController(view)
     }
 
     @Test
@@ -99,8 +100,7 @@
         val view = createViewMock()
         val argumentCaptor = ArgumentCaptor.forClass(OnPreDrawListener::class.java)
         unfoldConfig.isEnabled = true
-        controller = createController(view)
-        controller.init()
+        controller = createAndInitController(view)
 
         verify(view.viewTreeObserver).addOnPreDrawListener(argumentCaptor.capture())
         argumentCaptor.value.onPreDraw()
@@ -108,6 +108,64 @@
         verify(moveFromCenterAnimation).onViewsReady(any())
     }
 
+    @Test
+    fun touchIsWithinView_inBounds_returnsTrue() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_TOP + 1f)).isTrue()
+    }
+
+    @Test
+    fun touchIsWithinView_onTopLeftCorner_returnsTrue() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())).isTrue()
+    }
+
+    @Test
+    fun touchIsWithinView_onBottomRightCorner_returnsTrue() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(
+            VIEW_RIGHT.toFloat(), VIEW_BOTTOM.toFloat())
+        ).isTrue()
+    }
+
+    @Test
+    fun touchIsWithinView_xTooSmall_returnsFalse() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(VIEW_LEFT - 1f, VIEW_TOP + 1f)).isFalse()
+    }
+
+    @Test
+    fun touchIsWithinView_xTooLarge_returnsFalse() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(VIEW_RIGHT + 1f, VIEW_TOP + 1f)).isFalse()
+    }
+
+    @Test
+    fun touchIsWithinView_yTooSmall_returnsFalse() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_TOP - 1f)).isFalse()
+    }
+
+    @Test
+    fun touchIsWithinView_yTooLarge_returnsFalse() {
+        val view = createViewMockWithScreenLocation()
+        controller = createAndInitController(view)
+
+        assertThat(controller.touchIsWithinView(VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse()
+    }
+
     private fun createViewMock(): PhoneStatusBarView {
         val view = spy(view)
         val viewTreeObserver = mock(ViewTreeObserver::class.java)
@@ -116,12 +174,23 @@
         return view
     }
 
-    private fun createController(view: PhoneStatusBarView): PhoneStatusBarViewController {
+    private fun createViewMockWithScreenLocation(): PhoneStatusBarView {
+        val view = spy(view)
+        val location = IntArray(2)
+        location[0] = VIEW_LEFT
+        location[1] = VIEW_TOP
+        `when`(view.locationOnScreen).thenReturn(location)
+        return view
+    }
+
+    private fun createAndInitController(view: PhoneStatusBarView): PhoneStatusBarViewController {
         return PhoneStatusBarViewController.Factory(
             Optional.of(sysuiUnfoldComponent),
             Optional.of(progressProvider),
             configurationController
-        ).create(view, touchEventHandler)
+        ).create(view, touchEventHandler).also {
+            it.init()
+        }
     }
 
     private class UnfoldConfig : UnfoldTransitionConfig {
@@ -142,3 +211,8 @@
         }
     }
 }
+
+private const val VIEW_LEFT = 30
+private const val VIEW_RIGHT = 100
+private const val VIEW_TOP = 40
+private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
index 0131293..aabf923 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarCommandQueueCallbacksTest.java
@@ -36,7 +36,6 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.assist.AssistManager;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.DisableFlagsLogger;
@@ -46,8 +45,6 @@
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
 
 import org.junit.Before;
@@ -84,6 +81,7 @@
     @Mock private VibratorHelper mVibratorHelper;
     @Mock private Vibrator mVibrator;
     @Mock private LightBarController mLightBarController;
+    @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
     StatusBarCommandQueueCallbacks mSbcqCallbacks;
 
@@ -112,8 +110,7 @@
                 mStatusBarStateController,
                 mNotificationShadeWindowView,
                 mNotificationStackScrollLayoutController,
-                new StatusBarHideIconsForBouncerManager(
-                        mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()),
+                mStatusBarHideIconsForBouncerManager,
                 mPowerManager,
                 mVibratorHelper,
                 Optional.of(mVibrator),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 554e1d9..69f2bd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -148,6 +148,7 @@
 import com.android.systemui.statusbar.policy.UserInfoControllerImpl;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
+import com.android.systemui.statusbar.window.StatusBarWindowStateController;
 import com.android.systemui.util.WallpaperController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.concurrency.MessageRouterImpl;
@@ -229,6 +230,7 @@
     @Mock private DynamicPrivacyController mDynamicPrivacyController;
     @Mock private AutoHideController mAutoHideController;
     @Mock private StatusBarWindowController mStatusBarWindowController;
+    @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
     @Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
     @Mock private UserSwitcherController mUserSwitcherController;
     @Mock private NetworkController mNetworkController;
@@ -264,6 +266,7 @@
     @Mock private BrightnessSliderController.Factory mBrightnessSliderFactory;
     @Mock private WallpaperController mWallpaperController;
     @Mock private OngoingCallController mOngoingCallController;
+    @Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
     @Mock private LockscreenShadeTransitionController mLockscreenTransitionController;
     @Mock private FeatureFlags mFeatureFlags;
     @Mock private NotificationVisibilityProvider mVisibilityProvider;
@@ -376,6 +379,7 @@
                 mLightBarController,
                 mAutoHideController,
                 mStatusBarWindowController,
+                mStatusBarWindowStateController,
                 mKeyguardUpdateMonitor,
                 mStatusBarSignalPolicy,
                 mPulseExpansionHandler,
@@ -451,7 +455,7 @@
                 mScreenOffAnimationController,
                 mWallpaperController,
                 mOngoingCallController,
-                new StatusBarHideIconsForBouncerManager(mCommandQueue, mMainExecutor, mDumpManager),
+                mStatusBarHideIconsForBouncerManager,
                 mLockscreenTransitionController,
                 mFeatureFlags,
                 mKeyguardUnlockAnimationController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index b97f053..a630840 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -37,7 +37,6 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiBaseFragmentTest;
-import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogcatEchoTracker;
@@ -57,8 +56,6 @@
 import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
 import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -96,6 +93,8 @@
     private HeadsUpAppearanceController mHeadsUpAppearanceController;
     @Mock
     private NotificationPanelViewController mNotificationPanelViewController;
+    @Mock
+    private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
     public CollapsedStatusBarFragmentTest() {
         super(CollapsedStatusBarFragment.class);
@@ -325,8 +324,7 @@
                 new PanelExpansionStateManager(),
                 mock(FeatureFlags.class),
                 mStatusBarIconController,
-                new StatusBarHideIconsForBouncerManager(
-                        mCommandQueue, new FakeExecutor(new FakeSystemClock()), new DumpManager()),
+                mStatusBarHideIconsForBouncerManager,
                 mKeyguardStateController,
                 mNotificationPanelViewController,
                 mNetworkController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
new file mode 100644
index 0000000..8576d4f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2021 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.systemui.statusbar.window
+
+import android.app.StatusBarManager.WindowVisibleState
+import android.app.StatusBarManager.WINDOW_NAVIGATION_BAR
+import android.app.StatusBarManager.WINDOW_STATE_HIDDEN
+import android.app.StatusBarManager.WINDOW_STATE_SHOWING
+import android.app.StatusBarManager.WINDOW_STATUS_BAR
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.CommandQueue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+class StatusBarWindowStateControllerTest : SysuiTestCase() {
+    private lateinit var controller: StatusBarWindowStateController
+    private lateinit var callback: CommandQueue.Callbacks
+
+    @Mock
+    private lateinit var commandQueue: CommandQueue
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        controller = StatusBarWindowStateController(DISPLAY_ID, commandQueue)
+
+        val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java)
+        verify(commandQueue).addCallback(callbackCaptor.capture())
+        callback = callbackCaptor.value!!
+    }
+
+    @Test
+    fun setWindowState_notSameDisplayId_listenersNotNotified() {
+        val listener = TestListener()
+        controller.addListener(listener)
+
+        callback.setWindowState(DISPLAY_ID + 1, WINDOW_STATUS_BAR, WINDOW_STATE_HIDDEN)
+
+        assertThat(listener.state).isNull()
+    }
+
+    @Test
+    fun setWindowState_notStatusBarWindow_listenersNotNotified() {
+        val listener = TestListener()
+        controller.addListener(listener)
+
+        callback.setWindowState(DISPLAY_ID, WINDOW_NAVIGATION_BAR, WINDOW_STATE_HIDDEN)
+
+        assertThat(listener.state).isNull()
+    }
+
+    @Test
+    fun setWindowState_sameState_listenersNotNotified() {
+        val listener = TestListener()
+        controller.addListener(listener)
+
+        callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, WINDOW_STATE_SHOWING)
+
+        assertThat(listener.state).isNull()
+    }
+
+    @Test
+    fun setWindowState_newState_listenersNotified() {
+        val listener = TestListener()
+        controller.addListener(listener)
+        val newState = WINDOW_STATE_HIDDEN
+
+        callback.setWindowState(DISPLAY_ID, WINDOW_STATUS_BAR, newState)
+
+        assertThat(listener.state).isEqualTo(newState)
+    }
+
+    private class TestListener : StatusBarWindowStateListener {
+        @WindowVisibleState var state: Int? = null
+        override fun onStatusBarWindowStateChanged(@WindowVisibleState state: Int) {
+            this.state = state
+        }
+    }
+}
+
+private const val DISPLAY_ID = 10