[Media TTT] Don't hide the chip if the user taps the chip.

Fixes: 214274529
Fixes: 211487971
Test: manual (verify tapping chip doesn't hide chip)
Test: media.taptotransfer tests
Change-Id: Iba58f1d4adb694502a2f847c2d75779ff7fa92c0
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 6ec2b6e..15b8f13 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -22,6 +22,7 @@
 import android.graphics.PixelFormat
 import android.view.Gravity
 import android.view.LayoutInflater
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -31,6 +32,7 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
 
 /**
  * A superclass controller that provides common functionality for showing chips on the sender device
@@ -42,6 +44,7 @@
 abstract class MediaTttChipControllerCommon<T : MediaTttChipState>(
     internal val context: Context,
     private val windowManager: WindowManager,
+    private val viewUtil: ViewUtil,
     @Main private val mainExecutor: DelayableExecutor,
     private val tapGestureDetector: TapGestureDetector,
     @LayoutRes private val chipLayoutRes: Int
@@ -84,7 +87,7 @@
 
         // Add view if necessary
         if (oldChipView == null) {
-            tapGestureDetector.addOnGestureDetectedCallback(TAG, this::removeChip)
+            tapGestureDetector.addOnGestureDetectedCallback(TAG, this::onScreenTapped)
             windowManager.addView(chipView, windowLayoutParams)
         }
 
@@ -127,6 +130,15 @@
         appIconView.setImageDrawable(appIcon)
         appIconView.visibility = visibility
     }
+
+    private fun onScreenTapped(e: MotionEvent) {
+        val view = chipView ?: return
+        // If the tap is within the chip bounds, we shouldn't hide the chip (in case users think the
+        // chip is tappable).
+        if (!viewUtil.touchIsWithinView(view, e.x, e.y)) {
+            removeChip()
+        }
+    }
 }
 
 // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index b6f1aea..3d43ebe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
 import javax.inject.Inject
 
 /**
@@ -43,11 +44,17 @@
     commandQueue: CommandQueue,
     context: Context,
     windowManager: WindowManager,
+    viewUtil: ViewUtil,
     mainExecutor: DelayableExecutor,
     tapGestureDetector: TapGestureDetector,
     @Main private val mainHandler: Handler,
 ) : MediaTttChipControllerCommon<ChipStateReceiver>(
-    context, windowManager, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip_receiver
+    context,
+    windowManager,
+    viewUtil,
+    mainExecutor,
+    tapGestureDetector,
+    R.layout.media_ttt_chip_receiver
 ) {
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferReceiverDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index fef17fdc..180e4ee 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.gesture.TapGestureDetector
 import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.view.ViewUtil
 import javax.inject.Inject
 
 /**
@@ -43,10 +44,11 @@
     commandQueue: CommandQueue,
     context: Context,
     windowManager: WindowManager,
+    viewUtil: ViewUtil,
     @Main mainExecutor: DelayableExecutor,
     tapGestureDetector: TapGestureDetector,
 ) : MediaTttChipControllerCommon<ChipStateSender>(
-    context, windowManager, mainExecutor,  tapGestureDetector, R.layout.media_ttt_chip
+    context, windowManager, viewUtil, mainExecutor,  tapGestureDetector, R.layout.media_ttt_chip
 ) {
     private val commandQueueCallbacks = object : CommandQueue.Callbacks {
         override fun updateMediaTapToTransferSenderDisplay(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
index 76766b0..3a4731a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt
@@ -22,6 +22,7 @@
 import android.view.Choreographer
 import android.view.Display
 import android.view.InputEvent
+import android.view.MotionEvent
 import com.android.systemui.shared.system.InputChannelCompat
 import com.android.systemui.shared.system.InputMonitorCompat
 
@@ -43,13 +44,17 @@
      * Active callbacks, each associated with a tag. Gestures will only be monitored if
      * [callbacks.size] > 0.
      */
-    private val callbacks: MutableMap<String, () -> Unit> = mutableMapOf()
+    private val callbacks: MutableMap<String, (MotionEvent) -> Unit> = mutableMapOf()
 
     private var inputMonitor: InputMonitorCompat? = null
     private var inputReceiver: InputChannelCompat.InputEventReceiver? = null
 
-    /** Adds a callback that will be triggered when the tap gesture is detected. */
-    fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) {
+    /**
+     * Adds a callback that will be triggered when the tap gesture is detected.
+     *
+     * The callback receive the last motion event in the gesture.
+     */
+    fun addOnGestureDetectedCallback(tag: String, callback: (MotionEvent) -> Unit) {
         val callbacksWasEmpty = callbacks.isEmpty()
         callbacks[tag] = callback
         if (callbacksWasEmpty) {
@@ -68,9 +73,12 @@
     /** Triggered each time a touch event occurs (and at least one callback is registered). */
     abstract fun onInputEvent(ev: InputEvent)
 
-    /** Should be called by subclasses when their specific gesture is detected. */
-    internal fun onGestureDetected() {
-        callbacks.values.forEach { it.invoke() }
+    /**
+     * Should be called by subclasses when their specific gesture is detected with the last motion
+     * event in the gesture.
+     */
+    internal fun onGestureDetected(e: MotionEvent) {
+        callbacks.values.forEach { it.invoke(e) }
     }
 
     /** Start listening to touch events. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
index fcb285a..6115819 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt
@@ -80,7 +80,7 @@
                 ) {
                     monitoringCurrentTouch = false
                     logger.logGestureDetected(ev.y.toInt())
-                    onGestureDetected()
+                    onGestureDetected(ev)
                 }
             }
             ACTION_CANCEL, ACTION_UP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
index 4107ce2..7ffb07a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt
@@ -33,8 +33,8 @@
 ) : GenericGestureDetector(TapGestureDetector::class.simpleName!!) {
 
     private val gestureListener = object : GestureDetector.SimpleOnGestureListener() {
-        override fun onSingleTapUp(e: MotionEvent?): Boolean {
-            onGestureDetected()
+        override fun onSingleTapUp(e: MotionEvent): Boolean {
+            onGestureDetected(e)
             return true
         }
     }
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 224b2e4..9da2ef7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
 import com.android.systemui.util.ViewController
 import com.android.systemui.util.kotlin.getOrNull
+import com.android.systemui.util.view.ViewUtil
 
 import java.util.Optional
 
@@ -43,6 +44,7 @@
     @Named(UNFOLD_STATUS_BAR) private val progressProvider: ScopedUnfoldTransitionProgressProvider?,
     private val moveFromCenterAnimationController: StatusBarMoveFromCenterAnimationController?,
     private val userSwitcherController: StatusBarUserSwitcherController,
+    private val viewUtil: ViewUtil,
     touchEventHandler: PhoneStatusBarView.TouchEventHandler,
     private val configurationController: ConfigurationController
 ) : ViewController<PhoneStatusBarView>(view) {
@@ -118,12 +120,7 @@
      * 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
+        return viewUtil.touchIsWithinView(mView, x, y)
     }
 
     class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
@@ -163,6 +160,7 @@
         @Named(UNFOLD_STATUS_BAR)
         private val progressProvider: Optional<ScopedUnfoldTransitionProgressProvider>,
         private val userSwitcherController: StatusBarUserSwitcherController,
+        private val viewUtil: ViewUtil,
         private val configurationController: ConfigurationController
     ) {
         fun create(
@@ -174,6 +172,7 @@
                 progressProvider.getOrNull(),
                 unfoldComponent.getOrNull()?.getStatusBarMoveFromCenterAnimationController(),
                 userSwitcherController,
+                viewUtil,
                 touchEventHandler,
                 configurationController
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
index c7f7258..8750dec 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt
@@ -295,7 +295,7 @@
             swipeStatusBarAwayGestureHandler.ifPresent { it.removeOnGestureDetectedCallback(TAG) }
         } else {
             swipeStatusBarAwayGestureHandler.ifPresent {
-                it.addOnGestureDetectedCallback(TAG, this::onSwipeAwayGestureDetected)
+                it.addOnGestureDetectedCallback(TAG) { _ -> onSwipeAwayGestureDetected() }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
new file mode 100644
index 0000000..613a797
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt
@@ -0,0 +1,26 @@
+package com.android.systemui.util.view
+
+import android.view.View
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A class with generic view utility methods.
+ *
+ * Doesn't use static methods so that it can be easily mocked out in tests.
+ */
+@SysUISingleton
+class ViewUtil @Inject constructor() {
+    /**
+     * Returns true if the given (x, y) point (in screen coordinates) is within the status bar
+     * view's range and false otherwise.
+     */
+    fun touchIsWithinView(view: View, x: Float, y: Float): Boolean {
+        val left = view.locationOnScreen[0]
+        val top = view.locationOnScreen[1]
+        return left <= x &&
+                x <= left + view.width &&
+                top <= y &&
+                y <= top + view.height
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index 28de176..809b890 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.graphics.drawable.Drawable
+import android.view.MotionEvent
 import android.view.View
 import android.view.ViewGroup
 import android.view.WindowManager
@@ -30,7 +31,10 @@
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -39,6 +43,7 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -52,6 +57,8 @@
     @Mock
     private lateinit var windowManager: WindowManager
     @Mock
+    private lateinit var viewUtil: ViewUtil
+    @Mock
     private lateinit var tapGestureDetector: TapGestureDetector
 
     @Before
@@ -62,7 +69,7 @@
         fakeExecutor = FakeExecutor(fakeClock)
 
         controllerCommon = TestControllerCommon(
-            context, windowManager, fakeExecutor, tapGestureDetector
+            context, windowManager, viewUtil, fakeExecutor, tapGestureDetector
         )
     }
 
@@ -169,6 +176,40 @@
                 .isEqualTo(state.getAppName(context))
     }
 
+    @Test
+    fun tapGestureDetected_outsideViewBounds_viewHidden() {
+        controllerCommon.displayChip(getState())
+        whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(false)
+        val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
+        verify(tapGestureDetector).addOnGestureDetectedCallback(
+            any(), capture(gestureCallbackCaptor)
+        )
+        val callback = gestureCallbackCaptor.value!!
+
+        callback.invoke(
+            MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        )
+
+        verify(windowManager).removeView(any())
+    }
+
+    @Test
+    fun tapGestureDetected_insideViewBounds_viewNotHidden() {
+        controllerCommon.displayChip(getState())
+        whenever(viewUtil.touchIsWithinView(any(), any(), any())).thenReturn(true)
+        val gestureCallbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
+        verify(tapGestureDetector).addOnGestureDetectedCallback(
+            any(), capture(gestureCallbackCaptor)
+        )
+        val callback = gestureCallbackCaptor.value!!
+
+        callback.invoke(
+            MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+        )
+
+        verify(windowManager, never()).removeView(any())
+    }
+
     private fun getState() = MediaTttChipState(PACKAGE_NAME)
 
     private fun getChipView(): ViewGroup {
@@ -182,10 +223,11 @@
     inner class TestControllerCommon(
         context: Context,
         windowManager: WindowManager,
+        viewUtil: ViewUtil,
         @Main mainExecutor: DelayableExecutor,
         tapGestureDetector: TapGestureDetector,
     ) : MediaTttChipControllerCommon<MediaTttChipState>(
-        context, windowManager, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip
+        context, windowManager, viewUtil, mainExecutor, tapGestureDetector, R.layout.media_ttt_chip
     ) {
         override fun updateChipView(chipState: MediaTttChipState, currentChipView: ViewGroup) {
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index e5f4df6..86d4c1b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -37,6 +37,7 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -61,6 +62,8 @@
     @Mock
     private lateinit var windowManager: WindowManager
     @Mock
+    private lateinit var viewUtil: ViewUtil
+    @Mock
     private lateinit var commandQueue: CommandQueue
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
@@ -81,6 +84,7 @@
             commandQueue,
             context,
             windowManager,
+            viewUtil,
             FakeExecutor(FakeSystemClock()),
             TapGestureDetector(context),
             Handler.getMain(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index e5ba3f3..88888f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -38,6 +38,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
+import com.android.systemui.util.view.ViewUtil
+
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -62,6 +64,8 @@
     @Mock
     private lateinit var windowManager: WindowManager
     @Mock
+    private lateinit var viewUtil: ViewUtil
+    @Mock
     private lateinit var commandQueue: CommandQueue
     private lateinit var commandQueueCallback: CommandQueue.Callbacks
     private lateinit var fakeAppIconDrawable: Drawable
@@ -82,6 +86,7 @@
             commandQueue,
             context,
             windowManager,
+            viewUtil,
             FakeExecutor(FakeSystemClock()),
             TapGestureDetector(context)
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
index c038903..ea06647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt
@@ -111,7 +111,7 @@
 
         override fun onInputEvent(ev: InputEvent) {
             if (ev is MotionEvent && ev.x == CORRECT_X) {
-                onGestureDetected()
+                onGestureDetected(ev)
             }
         }
 
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 5891161..9ab88dc 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
@@ -31,6 +31,7 @@
 import com.android.systemui.unfold.SysUIUnfoldComponent
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
+import com.android.systemui.util.view.ViewUtil
 import com.android.systemui.util.mockito.any
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -63,6 +64,8 @@
     private lateinit var configurationController: ConfigurationController
     @Mock
     private lateinit var userSwitcherController: StatusBarUserSwitcherController
+    @Mock
+    private lateinit var viewUtil: ViewUtil
 
     private lateinit var view: PhoneStatusBarView
     private lateinit var controller: PhoneStatusBarViewController
@@ -80,7 +83,6 @@
             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 = createAndInitController(view)
@@ -111,64 +113,6 @@
         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)
@@ -177,20 +121,12 @@
         return view
     }
 
-    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),
             userSwitcherController,
+            viewUtil,
             configurationController
         ).create(view, touchEventHandler).also {
             it.init()
@@ -215,8 +151,3 @@
         }
     }
 }
-
-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/util/view/ViewUtilTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
new file mode 100644
index 0000000..dead159
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt
@@ -0,0 +1,72 @@
+package com.android.systemui.util.view
+
+import android.view.View
+import android.widget.TextView
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+
+@SmallTest
+class ViewUtilTest : SysuiTestCase() {
+    private val viewUtil = ViewUtil()
+    private lateinit var view: View
+
+    @Before
+    fun setUp() {
+        view = TextView(context)
+        view.setLeftTopRightBottom(VIEW_LEFT, VIEW_TOP, VIEW_RIGHT, VIEW_BOTTOM)
+
+        view = spy(view)
+        val location = IntArray(2)
+        location[0] = VIEW_LEFT
+        location[1] = VIEW_TOP
+        `when`(view.locationOnScreen).thenReturn(location)
+    }
+
+    @Test
+    fun touchIsWithinView_inBounds_returnsTrue() {
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_TOP + 1f)).isTrue()
+    }
+
+    @Test
+    fun touchIsWithinView_onTopLeftCorner_returnsTrue() {
+        assertThat(viewUtil.touchIsWithinView(
+            view, VIEW_LEFT.toFloat(), VIEW_TOP.toFloat())
+        ).isTrue()
+    }
+
+    @Test
+    fun touchIsWithinView_onBottomRightCorner_returnsTrue() {
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_RIGHT.toFloat(), VIEW_BOTTOM.toFloat()))
+            .isTrue()
+    }
+
+    @Test
+    fun touchIsWithinView_xTooSmall_returnsFalse() {
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT - 1f, VIEW_TOP + 1f)).isFalse()
+    }
+
+    @Test
+    fun touchIsWithinView_xTooLarge_returnsFalse() {
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_RIGHT + 1f, VIEW_TOP + 1f)).isFalse()
+    }
+
+    @Test
+    fun touchIsWithinView_yTooSmall_returnsFalse() {
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_TOP - 1f)).isFalse()
+    }
+
+    @Test
+    fun touchIsWithinView_yTooLarge_returnsFalse() {
+        assertThat(viewUtil.touchIsWithinView(view, VIEW_LEFT + 1f, VIEW_BOTTOM + 1f)).isFalse()
+    }
+}
+
+private const val VIEW_LEFT = 30
+private const val VIEW_RIGHT = 100
+private const val VIEW_TOP = 40
+private const val VIEW_BOTTOM = 100