From 3aa5b9ed5a16b870b87f0d11c95360f5ef04ac42 Mon Sep 17 00:00:00 2001 From: Caitlin Cassidy Date: Wed, 16 Feb 2022 22:01:11 +0000 Subject: [PATCH] [Media TTT] Hide the chip if the user taps the screen. This CL defines a generic gesture detector class since we now have multiple cases where we want to detect gestures across the whole display. Still TODO: Don't hide the chip if the user taps the chip itself. Bug: 214274529 Bug: 211487971 Test: manual: verify tapping the screen hides the chip Test: atest GenericGestureDetectorTest Test: atest media.taptotransfer Change-Id: I3951d0bbb3a04d5b82a54339a548b4e293bbf527 Merged-In: I3951d0bbb3a04d5b82a54339a548b4e293bbf527 --- .../common/MediaTttChipControllerCommon.kt | 5 + .../MediaTttChipControllerReceiver.kt | 4 +- .../sender/MediaTttChipControllerSender.kt | 6 +- .../gesture/GenericGestureDetector.kt | 102 ++++++++++++++ .../SwipeStatusBarAwayGestureHandler.kt | 70 ++-------- .../statusbar/gesture/TapGestureDetector.kt | 64 +++++++++ .../MediaTttChipControllerCommonTest.kt | 22 ++- .../MediaTttChipControllerReceiverTest.kt | 9 +- .../MediaTttChipControllerSenderTest.kt | 12 +- .../gesture/GenericGestureDetectorTest.kt | 130 ++++++++++++++++++ 10 files changed, 355 insertions(+), 69 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt create mode 100644 packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt create mode 100644 packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt 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 ee2fba09358b8..6ec2b6e89cb0e 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 @@ -29,6 +29,7 @@ import androidx.annotation.VisibleForTesting import com.android.internal.widget.CachingIconView import com.android.systemui.R import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.concurrency.DelayableExecutor /** @@ -42,6 +43,7 @@ abstract class MediaTttChipControllerCommon( internal val context: Context, private val windowManager: WindowManager, @Main private val mainExecutor: DelayableExecutor, + private val tapGestureDetector: TapGestureDetector, @LayoutRes private val chipLayoutRes: Int ) { /** The window layout parameters we'll use when attaching the view to a window. */ @@ -82,6 +84,7 @@ abstract class MediaTttChipControllerCommon( // Add view if necessary if (oldChipView == null) { + tapGestureDetector.addOnGestureDetectedCallback(TAG, this::removeChip) windowManager.addView(chipView, windowLayoutParams) } @@ -96,6 +99,7 @@ abstract class MediaTttChipControllerCommon( // TransferTriggered state: Once the user has initiated the transfer, they should be able // to move away from the receiver device but still see the status of the transfer. if (chipView == null) { return } + tapGestureDetector.removeOnGestureDetectedCallback(TAG) windowManager.removeView(chipView) chipView = null } @@ -128,5 +132,6 @@ abstract class MediaTttChipControllerCommon( // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and // UpdateMediaTapToTransferReceiverDisplayTest private const val WINDOW_TITLE = "Media Transfer Chip View" +private val TAG = MediaTttChipControllerCommon::class.simpleName!! @VisibleForTesting const val TIMEOUT_MILLIS = 3000L 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 214a8888e0b5b..b6f1aea9a384d 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 @@ -29,6 +29,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject @@ -43,9 +44,10 @@ class MediaTttChipControllerReceiver @Inject constructor( context: Context, windowManager: WindowManager, mainExecutor: DelayableExecutor, + tapGestureDetector: TapGestureDetector, @Main private val mainHandler: Handler, ) : MediaTttChipControllerCommon( - context, windowManager, mainExecutor, R.layout.media_ttt_chip_receiver + context, windowManager, 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 482e604a06351..fef17fdcbbdc0 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 @@ -30,6 +30,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCommon import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject @@ -42,9 +43,10 @@ class MediaTttChipControllerSender @Inject constructor( commandQueue: CommandQueue, context: Context, windowManager: WindowManager, - @Main private val mainExecutor: DelayableExecutor, + @Main mainExecutor: DelayableExecutor, + tapGestureDetector: TapGestureDetector, ) : MediaTttChipControllerCommon( - context, windowManager, mainExecutor, R.layout.media_ttt_chip + context, windowManager, 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 new file mode 100644 index 0000000000000..76766b0d0ef04 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2022 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.gesture + +import android.annotation.CallSuper +import android.os.Looper +import android.view.Choreographer +import android.view.Display +import android.view.InputEvent +import com.android.systemui.shared.system.InputChannelCompat +import com.android.systemui.shared.system.InputMonitorCompat + +/** + * An abstract class to help detect gestures that occur anywhere on the display (not specific to a + * certain view). + * + * This class handles starting/stopping the gesture detection system as well as + * registering/unregistering callbacks for when gestures occur. Note that the class will only listen + * for gestures when there's at least one callback registered. + * + * Subclasses should implement [onInputEvent] to detect their specific gesture. Once a specific + * gesture is detected, they should call [onGestureDetected] (which will notify the callbacks). + */ +abstract class GenericGestureDetector( + private val tag: String +) { + /** + * Active callbacks, each associated with a tag. Gestures will only be monitored if + * [callbacks.size] > 0. + */ + private val callbacks: MutableMap 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) { + val callbacksWasEmpty = callbacks.isEmpty() + callbacks[tag] = callback + if (callbacksWasEmpty) { + startGestureListening() + } + } + + /** Removes the callback. */ + fun removeOnGestureDetectedCallback(tag: String) { + callbacks.remove(tag) + if (callbacks.isEmpty()) { + stopGestureListening() + } + } + + /** 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() } + } + + /** Start listening to touch events. */ + @CallSuper + internal open fun startGestureListening() { + stopGestureListening() + + inputMonitor = InputMonitorCompat(tag, Display.DEFAULT_DISPLAY).also { + inputReceiver = it.getInputReceiver( + Looper.getMainLooper(), + Choreographer.getInstance(), + this::onInputEvent + ) + } + } + + /** Stop listening to touch events. */ + @CallSuper + internal open fun stopGestureListening() { + inputMonitor?.let { + inputMonitor = null + it.dispose() + } + inputReceiver?.let { + inputReceiver = null + it.dispose() + } + } +} 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 7cdf69d1ecbf4..fcb285a50ae72 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeStatusBarAwayGestureHandler.kt @@ -17,15 +17,13 @@ package com.android.systemui.statusbar.gesture import android.content.Context -import android.os.Looper -import android.view.Choreographer -import android.view.Display import android.view.InputEvent import android.view.MotionEvent -import android.view.MotionEvent.* +import android.view.MotionEvent.ACTION_CANCEL +import android.view.MotionEvent.ACTION_DOWN +import android.view.MotionEvent.ACTION_MOVE +import android.view.MotionEvent.ACTION_UP import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.shared.system.InputChannelCompat -import com.android.systemui.shared.system.InputMonitorCompat import com.android.systemui.statusbar.window.StatusBarWindowController import javax.inject.Inject @@ -38,43 +36,17 @@ open class SwipeStatusBarAwayGestureHandler @Inject constructor( context: Context, private val statusBarWindowController: StatusBarWindowController, private val logger: SwipeStatusBarAwayGestureLogger -) { - - /** - * Active callbacks, each associated with a tag. Gestures will only be monitored if - * [callbacks.size] > 0. - */ - private val callbacks: MutableMap Unit> = mutableMapOf() +) : GenericGestureDetector(SwipeStatusBarAwayGestureHandler::class.simpleName!!) { private var startY: Float = 0f private var startTime: Long = 0L private var monitoringCurrentTouch: Boolean = false - private var inputMonitor: InputMonitorCompat? = null - private var inputReceiver: InputChannelCompat.InputEventReceiver? = null - private var swipeDistanceThreshold: Int = context.resources.getDimensionPixelSize( com.android.internal.R.dimen.system_gestures_start_threshold ) - /** Adds a callback that will be triggered when the swipe away gesture is detected. */ - fun addOnGestureDetectedCallback(tag: String, callback: () -> Unit) { - val callbacksWasEmpty = callbacks.isEmpty() - callbacks[tag] = callback - if (callbacksWasEmpty) { - startGestureListening() - } - } - - /** Removes the callback. */ - fun removeOnGestureDetectedCallback(tag: String) { - callbacks.remove(tag) - if (callbacks.isEmpty()) { - stopGestureListening() - } - } - - private fun onInputEvent(ev: InputEvent) { + override fun onInputEvent(ev: InputEvent) { if (ev !is MotionEvent) { return } @@ -108,7 +80,7 @@ open class SwipeStatusBarAwayGestureHandler @Inject constructor( ) { monitoringCurrentTouch = false logger.logGestureDetected(ev.y.toInt()) - callbacks.values.forEach { it.invoke() } + onGestureDetected() } } ACTION_CANCEL, ACTION_UP -> { @@ -120,33 +92,15 @@ open class SwipeStatusBarAwayGestureHandler @Inject constructor( } } - /** Start listening for the swipe gesture. */ - private fun startGestureListening() { - stopGestureListening() - + override fun startGestureListening() { + super.startGestureListening() logger.logInputListeningStarted() - inputMonitor = InputMonitorCompat(TAG, Display.DEFAULT_DISPLAY).also { - inputReceiver = it.getInputReceiver( - Looper.getMainLooper(), - Choreographer.getInstance(), - this::onInputEvent - ) - } } - /** Stop listening for the swipe gesture. */ - private fun stopGestureListening() { - inputMonitor?.let { - logger.logInputListeningStopped() - inputMonitor = null - it.dispose() - } - inputReceiver?.let { - inputReceiver = null - it.dispose() - } + override fun stopGestureListening() { + super.stopGestureListening() + logger.logInputListeningStopped() } } private const val SWIPE_TIMEOUT_MS: Long = 500 -private val TAG = SwipeStatusBarAwayGestureHandler::class.simpleName diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt new file mode 100644 index 0000000000000..4107ce2593f1a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2022 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.gesture + +import android.content.Context +import android.view.GestureDetector +import android.view.InputEvent +import android.view.MotionEvent +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +/** + * A class to detect when a user taps the screen. To be notified when the tap is detected, add a + * callback via [addOnGestureDetectedCallback]. + */ +@SysUISingleton +class TapGestureDetector @Inject constructor( + private val context: Context +) : GenericGestureDetector(TapGestureDetector::class.simpleName!!) { + + private val gestureListener = object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapUp(e: MotionEvent?): Boolean { + onGestureDetected() + return true + } + } + + private var gestureDetector: GestureDetector? = null + + override fun onInputEvent(ev: InputEvent) { + if (ev !is MotionEvent) { + return + } + // Pass all events to [gestureDetector], which will then notify [gestureListener] when a tap + // is detected. + gestureDetector!!.onTouchEvent(ev) + } + + /** Start listening for the tap gesture. */ + override fun startGestureListening() { + super.startGestureListening() + gestureDetector = GestureDetector(context, gestureListener) + } + + /** Stop listening for the swipe gesture. */ + override fun stopGestureListening() { + super.stopGestureListening() + gestureDetector = null + } +} 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 ea0a5a42ad211..28de17664df1d 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 @@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase 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.concurrency.FakeExecutor import com.android.systemui.util.mockito.any @@ -50,6 +51,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { private lateinit var appIconDrawable: Drawable @Mock private lateinit var windowManager: WindowManager + @Mock + private lateinit var tapGestureDetector: TapGestureDetector @Before fun setUp() { @@ -58,23 +61,28 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { fakeClock = FakeSystemClock() fakeExecutor = FakeExecutor(fakeClock) - controllerCommon = TestControllerCommon(context, windowManager, fakeExecutor) + controllerCommon = TestControllerCommon( + context, windowManager, fakeExecutor, tapGestureDetector + ) } @Test - fun displayChip_chipAdded() { + fun displayChip_chipAddedAndGestureDetectionStarted() { controllerCommon.displayChip(getState()) verify(windowManager).addView(any(), any()) + verify(tapGestureDetector).addOnGestureDetectedCallback(any(), any()) } @Test - fun displayChip_twice_chipNotAddedTwice() { + fun displayChip_twice_chipAndGestureDetectionNotAddedTwice() { controllerCommon.displayChip(getState()) reset(windowManager) + reset(tapGestureDetector) controllerCommon.displayChip(getState()) verify(windowManager, never()).addView(any(), any()) + verify(tapGestureDetector, never()).addOnGestureDetectedCallback(any(), any()) } @Test @@ -130,7 +138,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { } @Test - fun removeChip_chipRemoved() { + fun removeChip_chipRemovedAndGestureDetectionStopped() { // First, add the chip controllerCommon.displayChip(getState()) @@ -138,6 +146,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { controllerCommon.removeChip() verify(windowManager).removeView(any()) + verify(tapGestureDetector).removeOnGestureDetectedCallback(any()) } @Test @@ -174,8 +183,9 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() { context: Context, windowManager: WindowManager, @Main mainExecutor: DelayableExecutor, - ) : MediaTttChipControllerCommon( - context, windowManager, mainExecutor, R.layout.media_ttt_chip + tapGestureDetector: TapGestureDetector, + ) : MediaTttChipControllerCommon( + context, windowManager, 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 117a6c8985108..e5f4df6c622a6 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 @@ -22,6 +22,8 @@ import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info import android.os.Handler +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.WindowManager @@ -30,6 +32,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -37,6 +40,7 @@ import com.android.systemui.util.time.FakeSystemClock 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.Mockito.never @@ -45,6 +49,8 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper class MediaTttChipControllerReceiverTest : SysuiTestCase() { private lateinit var controllerReceiver: MediaTttChipControllerReceiver @@ -76,7 +82,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() { context, windowManager, FakeExecutor(FakeSystemClock()), - Handler.getMain() + TapGestureDetector(context), + Handler.getMain(), ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 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 b440064298281..e5ba3f3d87bf0 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 @@ -21,6 +21,8 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.media.MediaRoute2Info +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper import android.view.View import android.view.WindowManager import android.widget.ImageView @@ -31,6 +33,7 @@ import com.android.internal.statusbar.IUndoMediaTransferCallback import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.gesture.TapGestureDetector import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq @@ -38,6 +41,7 @@ import com.android.systemui.util.time.FakeSystemClock 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.Mockito.never @@ -46,6 +50,8 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper class MediaTttChipControllerSenderTest : SysuiTestCase() { private lateinit var controllerSender: MediaTttChipControllerSender @@ -73,7 +79,11 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() { context.setMockPackageManager(packageManager) controllerSender = MediaTttChipControllerSender( - commandQueue, context, windowManager, FakeExecutor(FakeSystemClock()) + commandQueue, + context, + windowManager, + FakeExecutor(FakeSystemClock()), + TapGestureDetector(context) ) val callbackCaptor = ArgumentCaptor.forClass(CommandQueue.Callbacks::class.java) 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 new file mode 100644 index 0000000000000..c0389034b4f23 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt @@ -0,0 +1,130 @@ +package com.android.systemui.statusbar.gesture + +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.InputEvent +import android.view.MotionEvent +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.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class GenericGestureDetectorTest : SysuiTestCase() { + + private lateinit var gestureDetector: TestGestureDetector + + @Before + fun setUp() { + gestureDetector = TestGestureDetector() + } + + @Test + fun noCallbacksRegistered_notGestureListening() { + assertThat(gestureDetector.isGestureListening).isFalse() + } + + @Test + fun callbackRegistered_isGestureListening() { + gestureDetector.addOnGestureDetectedCallback("tag"){} + + assertThat(gestureDetector.isGestureListening).isTrue() + } + + @Test + fun multipleCallbacksRegistered_isGestureListening() { + gestureDetector.addOnGestureDetectedCallback("tag"){} + gestureDetector.addOnGestureDetectedCallback("tag2"){} + + assertThat(gestureDetector.isGestureListening).isTrue() + } + + @Test + fun allCallbacksUnregistered_notGestureListening() { + gestureDetector.addOnGestureDetectedCallback("tag"){} + gestureDetector.addOnGestureDetectedCallback("tag2"){} + + gestureDetector.removeOnGestureDetectedCallback("tag") + gestureDetector.removeOnGestureDetectedCallback("tag2") + + assertThat(gestureDetector.isGestureListening).isFalse() + } + + @Test + fun someButNotAllCallbacksUnregistered_isGestureListening() { + gestureDetector.addOnGestureDetectedCallback("tag"){} + gestureDetector.addOnGestureDetectedCallback("tag2"){} + + gestureDetector.removeOnGestureDetectedCallback("tag2") + + assertThat(gestureDetector.isGestureListening).isTrue() + } + + @Test + fun onInputEvent_meetsGestureCriteria_allCallbacksNotified() { + var callbackNotified = false + gestureDetector.addOnGestureDetectedCallback("tag"){ + callbackNotified = true + } + + gestureDetector.onInputEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X, 0f, 0) + ) + + assertThat(callbackNotified).isTrue() + } + + @Test + fun onInputEvent_doesNotMeetGestureCriteria_callbackNotNotified() { + var callbackNotified = false + gestureDetector.addOnGestureDetectedCallback("tag"){ + callbackNotified = true + } + + gestureDetector.onInputEvent( + MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X - 5, 0f, 0) + ) + + assertThat(callbackNotified).isFalse() + } + + @Test + fun callbackUnregisteredThenGestureDetected_oldCallbackNotNotified() { + var oldCallbackNotified = false + gestureDetector.addOnGestureDetectedCallback("tag"){ + oldCallbackNotified = true + } + gestureDetector.addOnGestureDetectedCallback("tag2"){} + + gestureDetector.removeOnGestureDetectedCallback("tag") + gestureDetector.onInputEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, CORRECT_X, 0f, 0)) + + assertThat(oldCallbackNotified).isFalse() + } + + inner class TestGestureDetector : GenericGestureDetector("fakeTag") { + var isGestureListening = false + + override fun onInputEvent(ev: InputEvent) { + if (ev is MotionEvent && ev.x == CORRECT_X) { + onGestureDetected() + } + } + + override fun startGestureListening() { + super.startGestureListening() + isGestureListening = true + } + + override fun stopGestureListening() { + super.stopGestureListening() + isGestureListening = false + } + } +} + +private const val CORRECT_X = 1234f