Merge "Multi-shade foundation - integration (5/5)." into udc-dev
diff --git a/packages/SystemUI/res/layout/multi_shade.xml b/packages/SystemUI/res/layout/multi_shade.xml
new file mode 100644
index 0000000..78ff5f0
--- /dev/null
+++ b/packages/SystemUI/res/layout/multi_shade.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 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.
+  ~
+  -->
+
+<com.android.systemui.multishade.ui.view.MultiShadeView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" />
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 4abc176..fe9542b 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -110,6 +110,13 @@
         android:clipChildren="false"
         android:clipToPadding="false" />
 
+    <ViewStub
+        android:id="@+id/multi_shade_stub"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:inflatedId="@+id/multi_shade"
+        android:layout="@layout/multi_shade" />
+
     <com.android.systemui.biometrics.AuthRippleView
         android:id="@+id/auth_ripple"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
new file mode 100644
index 0000000..aecec39
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/multishade/ui/view/MultiShadeView.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2023 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.multishade.ui.view
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.compose.ComposeFacade
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.multishade.ui.viewmodel.MultiShadeViewModel
+import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.launch
+
+/**
+ * View that hosts the multi-shade system and acts as glue between legacy code and the
+ * implementation.
+ */
+class MultiShadeView(
+    context: Context,
+    attrs: AttributeSet?,
+) :
+    FrameLayout(
+        context,
+        attrs,
+    ) {
+
+    fun init(
+        interactor: MultiShadeInteractor,
+        clock: SystemClock,
+    ) {
+        repeatWhenAttached {
+            lifecycleScope.launch {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    addView(
+                        ComposeFacade.createMultiShadeView(
+                            context = context,
+                            viewModel =
+                                MultiShadeViewModel(
+                                    viewModelScope = this,
+                                    interactor = interactor,
+                                ),
+                            clock = clock,
+                        )
+                    )
+                }
+
+                // Here when destroyed.
+                removeAllViews()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 2899081..a716a6e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -23,7 +23,6 @@
 import android.media.AudioManager;
 import android.media.session.MediaSessionLegacyHelper;
 import android.os.PowerManager;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.GestureDetector;
 import android.view.InputDevice;
@@ -31,6 +30,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewStub;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.keyguard.AuthKeyguardMessageArea;
@@ -39,8 +39,10 @@
 import com.android.systemui.R;
 import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
 import com.android.systemui.classifier.FalsingCollector;
+import com.android.systemui.compose.ComposeFacade;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
@@ -49,6 +51,8 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder;
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor;
+import com.android.systemui.multishade.ui.view.MultiShadeView;
 import com.android.systemui.statusbar.DragDownHelper;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.NotificationInsetsController;
@@ -63,11 +67,13 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
 import com.android.systemui.statusbar.window.StatusBarWindowStateController;
+import com.android.systemui.util.time.SystemClock;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  * Controller for {@link NotificationShadeWindowView}.
@@ -115,6 +121,7 @@
                 mIsOcclusionTransitionRunning =
                     step.getTransitionState() == TransitionState.RUNNING;
             };
+    private final SystemClock mClock;
 
     @Inject
     public NotificationShadeWindowViewController(
@@ -142,7 +149,9 @@
             UdfpsOverlayInteractor udfpsOverlayInteractor,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            Provider<MultiShadeInteractor> multiShadeInteractorProvider,
+            SystemClock clock) {
         mLockscreenShadeTransitionController = transitionController;
         mFalsingCollector = falsingCollector;
         mStatusBarStateController = statusBarStateController;
@@ -175,6 +184,16 @@
 
         collectFlow(mView, keyguardTransitionInteractor.getLockscreenToDreamingTransition(),
                 mLockscreenToDreamingTransition);
+
+        mClock = clock;
+        if (ComposeFacade.INSTANCE.isComposeAvailable()
+                && featureFlags.isEnabled(Flags.DUAL_SHADE)) {
+            final ViewStub multiShadeViewStub = mView.findViewById(R.id.multi_shade_stub);
+            if (multiShadeViewStub != null) {
+                final MultiShadeView multiShadeView = (MultiShadeView) multiShadeViewStub.inflate();
+                multiShadeView.init(multiShadeInteractorProvider.get(), clock);
+            }
+        }
     }
 
     /**
@@ -269,7 +288,7 @@
                 mLockIconViewController.onTouchEvent(
                         ev,
                         () -> mService.wakeUpIfDozing(
-                                SystemClock.uptimeMillis(),
+                                mClock.uptimeMillis(),
                                 mView,
                                 "LOCK_ICON_TOUCH",
                                 PowerManager.WAKE_REASON_GESTURE)
@@ -453,7 +472,7 @@
 
     public void cancelCurrentTouch() {
         if (mTouchActive) {
-            final long now = SystemClock.uptimeMillis();
+            final long now = mClock.uptimeMillis();
             final MotionEvent event;
             if (mIsTrackpadGestureBackEnabled) {
                 event = MotionEvent.obtain(mDownEvent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
index 0dc2d17..bdb0e7e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -37,6 +37,9 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
 import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.NotificationInsetsController
@@ -50,8 +53,12 @@
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,10 +72,12 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper(setAsMainLooper = true)
 class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
+
     @Mock private lateinit var view: NotificationShadeWindowView
     @Mock private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var centralSurfaces: CentralSurfaces
@@ -102,6 +111,8 @@
 
     private lateinit var underTest: NotificationShadeWindowViewController
 
+    private lateinit var testScope: TestScope
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -115,8 +126,12 @@
         whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
             .thenReturn(emptyFlow<TransitionStep>())
 
-        val featureFlags = FakeFeatureFlags();
+        val featureFlags = FakeFeatureFlags()
         featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        featureFlags.set(Flags.DUAL_SHADE, false)
+
+        val inputProxy = MultiShadeInputProxy()
+        testScope = TestScope()
         underTest =
             NotificationShadeWindowViewController(
                 lockscreenShadeTransitionController,
@@ -144,6 +159,18 @@
                 keyguardTransitionInteractor,
                 primaryBouncerToGoneTransitionViewModel,
                 featureFlags,
+                {
+                    MultiShadeInteractor(
+                        applicationScope = testScope.backgroundScope,
+                        repository =
+                            MultiShadeRepository(
+                                applicationContext = context,
+                                inputProxy = inputProxy,
+                            ),
+                        inputProxy = inputProxy,
+                    )
+                },
+                FakeSystemClock(),
             )
         underTest.setupExpandedStatusBar()
 
@@ -156,147 +183,162 @@
     // tests need to be added to test the rest of handleDispatchTouchEvent.
 
     @Test
-    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
-        underTest.setStatusBarViewController(null)
+    fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(null)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        assertThat(returnVal).isFalse()
-    }
+            assertThat(returnVal).isFalse()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
-        whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
+    fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+            whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
 
-        verify(phoneStatusBarViewController).sendTouchToView(ev)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(ev)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        val downEvBelow =
-            MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
-        interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
+    fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            val downEvBelow =
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+            interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
 
-        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
-        whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+            val nextEvent =
+                MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0)
+            whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
 
-        verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
-        whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
+    fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
+            whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController).sendTouchToView(downEv)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
-        // Item we're testing
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
+    fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
+            // Item we're testing
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
-        assertThat(returnVal).isNull()
-    }
+            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isNull()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        // Item we're testing
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(false)
+    fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            // Item we're testing
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(false)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
-        assertThat(returnVal).isNull()
-    }
+            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isNull()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
-        // Item we're testing
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
+    fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
+            // Item we're testing
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController, never()).sendTouchToView(DOWN_EVENT)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
-        underTest.setStatusBarViewController(phoneStatusBarViewController)
-        whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
-        whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
-        whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
-            .thenReturn(true)
+    fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() =
+        testScope.runTest {
+            underTest.setStatusBarViewController(phoneStatusBarViewController)
+            whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+            whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+            whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+                .thenReturn(true)
 
-        // Down event first
-        interactionEventHandler.handleDispatchTouchEvent(downEv)
+            // Down event first
+            interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)
 
-        // Then another event
-        val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
-        whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+            // Then another event
+            val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+            whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
 
-        val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+            val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
 
-        verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
-        assertThat(returnVal).isTrue()
-    }
+            verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+            assertThat(returnVal).isTrue()
+        }
 
     @Test
-    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() {
-        // Down event within udfpsOverlay bounds while alternateBouncer is showing
-        whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(downEv)).thenReturn(false)
-        whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+    fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() =
+        testScope.runTest {
+            // Down event within udfpsOverlay bounds while alternateBouncer is showing
+            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT))
+                .thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
 
-        // Then touch should not be intercepted
-        val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(downEv)
-        assertThat(shouldIntercept).isFalse()
-    }
+            // Then touch should not be intercepted
+            val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)
+            assertThat(shouldIntercept).isFalse()
+        }
 
     @Test
-    fun testGetBouncerContainer() {
-        Mockito.clearInvocations(view)
-        underTest.bouncerContainer
-        verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
-    }
+    fun testGetBouncerContainer() =
+        testScope.runTest {
+            Mockito.clearInvocations(view)
+            underTest.bouncerContainer
+            verify(view).findViewById<ViewGroup>(R.id.keyguard_bouncer_container)
+        }
 
     @Test
-    fun testGetKeyguardMessageArea() {
-        underTest.keyguardMessageArea
-        verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
+    fun testGetKeyguardMessageArea() =
+        testScope.runTest {
+            underTest.keyguardMessageArea
+            verify(view).findViewById<ViewGroup>(R.id.keyguard_message_area)
+        }
+
+    companion object {
+        private val DOWN_EVENT = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        private const val VIEW_BOTTOM = 100
     }
 }
-
-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/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
deleted file mode 100644
index 2797440..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * Copyright (C) 2017 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.shade;
-
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
-import android.os.SystemClock;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.MotionEvent;
-import android.view.ViewGroup;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardSecurityContainerController;
-import com.android.keyguard.LockIconViewController;
-import com.android.keyguard.dagger.KeyguardBouncerComponent;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor;
-import com.android.systemui.classifier.FalsingCollectorFake;
-import com.android.systemui.dock.DockManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel;
-import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel;
-import com.android.systemui.statusbar.DragDownHelper;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.NotificationInsetsController;
-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.AmbientState;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.window.StatusBarWindowStateController;
-import com.android.systemui.tuner.TunerService;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-public class NotificationShadeWindowViewTest extends SysuiTestCase {
-
-    private NotificationShadeWindowView mView;
-    private NotificationShadeWindowViewController mController;
-
-    @Mock private TunerService mTunerService;
-    @Mock private DragDownHelper mDragDownHelper;
-    @Mock private SysuiStatusBarStateController mStatusBarStateController;
-    @Mock private ShadeController mShadeController;
-    @Mock private CentralSurfaces mCentralSurfaces;
-    @Mock private DockManager mDockManager;
-    @Mock private NotificationPanelViewController mNotificationPanelViewController;
-    @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
-    @Mock private NotificationShadeDepthController mNotificationShadeDepthController;
-    @Mock private NotificationShadeWindowController mNotificationShadeWindowController;
-    @Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
-    @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock private StatusBarWindowStateController mStatusBarWindowStateController;
-    @Mock private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock private LockIconViewController mLockIconViewController;
-    @Mock private KeyguardUnlockAnimationController mKeyguardUnlockAnimationController;
-    @Mock private AmbientState mAmbientState;
-    @Mock private PulsingGestureListener mPulsingGestureListener;
-    @Mock private KeyguardBouncerViewModel mKeyguardBouncerViewModel;
-    @Mock private KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory;
-    @Mock private KeyguardBouncerComponent mKeyguardBouncerComponent;
-    @Mock private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
-    @Mock private NotificationInsetsController mNotificationInsetsController;
-    @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor;
-    @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor;
-    @Mock private KeyguardTransitionInteractor mKeyguardTransitionInteractor;
-    @Mock private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel;
-
-    @Captor private ArgumentCaptor<NotificationShadeWindowView.InteractionEventHandler>
-            mInteractionEventHandlerCaptor;
-    private NotificationShadeWindowView.InteractionEventHandler mInteractionEventHandler;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mView = spy(new NotificationShadeWindowView(getContext(), null));
-        when(mView.findViewById(R.id.notification_stack_scroller))
-                .thenReturn(mNotificationStackScrollLayout);
-
-        when(mView.findViewById(R.id.keyguard_bouncer_container)).thenReturn(mock(ViewGroup.class));
-        when(mKeyguardBouncerComponentFactory.create(any(ViewGroup.class))).thenReturn(
-                mKeyguardBouncerComponent);
-        when(mKeyguardBouncerComponent.getSecurityContainerController()).thenReturn(
-                mKeyguardSecurityContainerController);
-
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        mDependency.injectTestDependency(ShadeController.class, mShadeController);
-
-        when(mDockManager.isDocked()).thenReturn(false);
-
-        when(mKeyguardTransitionInteractor.getLockscreenToDreamingTransition())
-                .thenReturn(emptyFlow());
-
-        FakeFeatureFlags featureFlags = new FakeFeatureFlags();
-        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false);
-        mController = new NotificationShadeWindowViewController(
-                mLockscreenShadeTransitionController,
-                new FalsingCollectorFake(),
-                mStatusBarStateController,
-                mDockManager,
-                mNotificationShadeDepthController,
-                mView,
-                mNotificationPanelViewController,
-                new ShadeExpansionStateManager(),
-                mNotificationStackScrollLayoutController,
-                mStatusBarKeyguardViewManager,
-                mStatusBarWindowStateController,
-                mLockIconViewController,
-                mCentralSurfaces,
-                mNotificationShadeWindowController,
-                mKeyguardUnlockAnimationController,
-                mNotificationInsetsController,
-                mAmbientState,
-                mPulsingGestureListener,
-                mKeyguardBouncerViewModel,
-                mKeyguardBouncerComponentFactory,
-                mAlternateBouncerInteractor,
-                mUdfpsOverlayInteractor,
-                mKeyguardTransitionInteractor,
-                mPrimaryBouncerToGoneTransitionViewModel,
-                featureFlags
-        );
-        mController.setupExpandedStatusBar();
-        mController.setDragDownHelper(mDragDownHelper);
-    }
-
-    @Test
-    public void testDragDownHelperCalledWhenDraggingDown() {
-        when(mDragDownHelper.isDraggingDown()).thenReturn(true);
-        long now = SystemClock.elapsedRealtime();
-        MotionEvent ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0 /* x */, 0 /* y */,
-                0 /* meta */);
-        mView.onTouchEvent(ev);
-        verify(mDragDownHelper).onTouchEvent(ev);
-        ev.recycle();
-    }
-
-    @Test
-    public void testInterceptTouchWhenShowingAltAuth() {
-        captureInteractionEventHandler();
-
-        // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        when(mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true);
-        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
-        // THEN we should intercept touch
-        assertTrue(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
-    }
-
-    @Test
-    public void testNoInterceptTouch() {
-        captureInteractionEventHandler();
-
-        // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false);
-        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
-        // THEN we shouldn't intercept touch
-        assertFalse(mInteractionEventHandler.shouldInterceptTouchEvent(mock(MotionEvent.class)));
-    }
-
-    @Test
-    public void testHandleTouchEventWhenShowingAltAuth() {
-        captureInteractionEventHandler();
-
-        // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
-        when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
-        when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
-
-        // THEN we should handle the touch
-        assertTrue(mInteractionEventHandler.handleTouchEvent(mock(MotionEvent.class)));
-    }
-
-    private void captureInteractionEventHandler() {
-        verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture());
-        mInteractionEventHandler = mInteractionEventHandlerCaptor.getValue();
-
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
new file mode 100644
index 0000000..5d0f408
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 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.shade
+
+import android.os.SystemClock
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityContainerController
+import com.android.keyguard.LockIconViewController
+import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.dock.DockManager
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
+import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
+import com.android.systemui.multishade.data.remoteproxy.MultiShadeInputProxy
+import com.android.systemui.multishade.data.repository.MultiShadeRepository
+import com.android.systemui.multishade.domain.interactor.MultiShadeInteractor
+import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.statusbar.DragDownHelper
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationInsetsController
+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.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NotificationShadeWindowViewTest : SysuiTestCase() {
+
+    @Mock private lateinit var dragDownHelper: DragDownHelper
+    @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+    @Mock private lateinit var shadeController: ShadeController
+    @Mock private lateinit var centralSurfaces: CentralSurfaces
+    @Mock private lateinit var dockManager: DockManager
+    @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+    @Mock private lateinit var notificationStackScrollLayout: NotificationStackScrollLayout
+    @Mock private lateinit var notificationShadeDepthController: NotificationShadeDepthController
+    @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+    @Mock
+    private lateinit var notificationStackScrollLayoutController:
+        NotificationStackScrollLayoutController
+    @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+    @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController
+    @Mock
+    private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+    @Mock private lateinit var lockIconViewController: LockIconViewController
+    @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+    @Mock private lateinit var ambientState: AmbientState
+    @Mock private lateinit var pulsingGestureListener: PulsingGestureListener
+    @Mock private lateinit var keyguardBouncerViewModel: KeyguardBouncerViewModel
+    @Mock private lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory
+    @Mock private lateinit var keyguardBouncerComponent: KeyguardBouncerComponent
+    @Mock
+    private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController
+    @Mock private lateinit var notificationInsetsController: NotificationInsetsController
+    @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    @Mock
+    private lateinit var primaryBouncerToGoneTransitionViewModel:
+        PrimaryBouncerToGoneTransitionViewModel
+    @Captor
+    private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+    @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
+
+    private lateinit var underTest: NotificationShadeWindowView
+    private lateinit var controller: NotificationShadeWindowViewController
+    private lateinit var interactionEventHandler: InteractionEventHandler
+    private lateinit var testScope: TestScope
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        underTest = spy(NotificationShadeWindowView(context, null))
+        whenever(
+                underTest.findViewById<NotificationStackScrollLayout>(
+                    R.id.notification_stack_scroller
+                )
+            )
+            .thenReturn(notificationStackScrollLayout)
+        whenever(underTest.findViewById<FrameLayout>(R.id.keyguard_bouncer_container))
+            .thenReturn(mock())
+        whenever(keyguardBouncerComponentFactory.create(any())).thenReturn(keyguardBouncerComponent)
+        whenever(keyguardBouncerComponent.securityContainerController)
+            .thenReturn(keyguardSecurityContainerController)
+        whenever(statusBarStateController.isDozing).thenReturn(false)
+        mDependency.injectTestDependency(ShadeController::class.java, shadeController)
+        whenever(dockManager.isDocked).thenReturn(false)
+        whenever(keyguardTransitionInteractor.lockscreenToDreamingTransition)
+            .thenReturn(emptyFlow())
+
+        val featureFlags = FakeFeatureFlags()
+        featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false)
+        featureFlags.set(Flags.DUAL_SHADE, false)
+        val inputProxy = MultiShadeInputProxy()
+        testScope = TestScope()
+        controller =
+            NotificationShadeWindowViewController(
+                lockscreenShadeTransitionController,
+                FalsingCollectorFake(),
+                statusBarStateController,
+                dockManager,
+                notificationShadeDepthController,
+                underTest,
+                notificationPanelViewController,
+                ShadeExpansionStateManager(),
+                notificationStackScrollLayoutController,
+                statusBarKeyguardViewManager,
+                statusBarWindowStateController,
+                lockIconViewController,
+                centralSurfaces,
+                notificationShadeWindowController,
+                keyguardUnlockAnimationController,
+                notificationInsetsController,
+                ambientState,
+                pulsingGestureListener,
+                keyguardBouncerViewModel,
+                keyguardBouncerComponentFactory,
+                alternateBouncerInteractor,
+                udfpsOverlayInteractor,
+                keyguardTransitionInteractor,
+                primaryBouncerToGoneTransitionViewModel,
+                featureFlags,
+                {
+                    MultiShadeInteractor(
+                        applicationScope = testScope.backgroundScope,
+                        repository =
+                            MultiShadeRepository(
+                                applicationContext = context,
+                                inputProxy = inputProxy,
+                            ),
+                        inputProxy = inputProxy,
+                    )
+                },
+                FakeSystemClock(),
+            )
+
+        controller.setupExpandedStatusBar()
+        controller.setDragDownHelper(dragDownHelper)
+    }
+
+    @Test
+    fun testDragDownHelperCalledWhenDraggingDown() =
+        testScope.runTest {
+            whenever(dragDownHelper.isDraggingDown).thenReturn(true)
+            val now = SystemClock.elapsedRealtime()
+            val ev = MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, 0f, 0f, 0 /* meta */)
+            underTest.onTouchEvent(ev)
+            verify(dragDownHelper).onTouchEvent(ev)
+            ev.recycle()
+        }
+
+    @Test
+    fun testInterceptTouchWhenShowingAltAuth() =
+        testScope.runTest {
+            captureInteractionEventHandler()
+
+            // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+            whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we should intercept touch
+            assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue()
+        }
+
+    @Test
+    fun testNoInterceptTouch() =
+        testScope.runTest {
+            captureInteractionEventHandler()
+
+            // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we shouldn't intercept touch
+            assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse()
+        }
+
+    @Test
+    fun testHandleTouchEventWhenShowingAltAuth() =
+        testScope.runTest {
+            captureInteractionEventHandler()
+
+            // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
+            whenever(statusBarStateController.isDozing).thenReturn(false)
+            whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true)
+            whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false)
+
+            // THEN we should handle the touch
+            assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue()
+        }
+
+    private fun captureInteractionEventHandler() {
+        verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
+        interactionEventHandler = interactionEventHandlerCaptor.value
+    }
+}