[Media TTT] Allow swiping up to dismiss the chipbar.
Bug: 262584940 Test: with flag off, verify that you can't swipe up to dismiss the chipbar Test: with flag on, verify that starting a swipe around the chipbar area and swiping up will dismiss the chipbar Test: atest ChipbarCoordinatorTest SwipeChipbarAwayGestureHandlerTest Change-Id: I8440b9b2d7d715981bbf8ee9e44d9202068d4a88 Change-Id: I38aa257855eacaaae6708c796f3f91ea4584342a
This commit is contained in:
@@ -341,6 +341,10 @@ object Flags {
|
||||
// TODO(b/263512203): Tracking Bug
|
||||
val MEDIA_EXPLICIT_INDICATOR = unreleasedFlag(911, "media_explicit_indicator", teamfood = true)
|
||||
|
||||
// TODO(b/265813373): Tracking Bug
|
||||
val MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE =
|
||||
unreleasedFlag(912, "media_ttt_dismiss_gesture", teamfood = true)
|
||||
|
||||
// 1000 - dock
|
||||
val SIMULATE_DOCK_THROUGH_CHARGING = releasedFlag(1000, "simulate_dock_through_charging")
|
||||
|
||||
|
||||
@@ -30,4 +30,8 @@ class MediaTttFlags @Inject constructor(private val featureFlags: FeatureFlags)
|
||||
/** Check whether the flag for the receiver success state is enabled. */
|
||||
fun isMediaTttReceiverSuccessRippleEnabled(): Boolean =
|
||||
featureFlags.isEnabled(Flags.MEDIA_TTT_RECEIVER_SUCCESS_RIPPLE)
|
||||
|
||||
/** True if the media transfer chip can be dismissed via a gesture. */
|
||||
fun isMediaTttDismissGestureEnabled(): Boolean =
|
||||
featureFlags.isEnabled(Flags.MEDIA_TAP_TO_TRANSFER_DISMISS_GESTURE)
|
||||
}
|
||||
|
||||
@@ -185,6 +185,7 @@ constructor(
|
||||
}
|
||||
},
|
||||
vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
|
||||
allowSwipeToDismiss = true,
|
||||
windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
|
||||
wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
|
||||
timeoutMs = chipStateSender.timeout,
|
||||
|
||||
@@ -31,6 +31,7 @@ import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
|
||||
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
|
||||
import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
|
||||
import androidx.annotation.CallSuper
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.systemui.CoreStartable
|
||||
import com.android.systemui.Dumpable
|
||||
import com.android.systemui.dagger.qualifiers.Main
|
||||
@@ -108,9 +109,10 @@ abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : Tempora
|
||||
* Whenever the current view disappears, the next-priority view will be displayed if it's still
|
||||
* valid.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
internal val activeViews: MutableList<DisplayInfo> = mutableListOf()
|
||||
|
||||
private fun getCurrentDisplayInfo(): DisplayInfo? {
|
||||
internal fun getCurrentDisplayInfo(): DisplayInfo? {
|
||||
return activeViews.getOrNull(0)
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ constructor(
|
||||
powerManager: PowerManager,
|
||||
private val falsingManager: FalsingManager,
|
||||
private val falsingCollector: FalsingCollector,
|
||||
private val swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler?,
|
||||
private val viewUtil: ViewUtil,
|
||||
private val vibratorHelper: VibratorHelper,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
@@ -105,6 +106,8 @@ constructor(
|
||||
commonWindowLayoutParams.apply { gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL) }
|
||||
|
||||
override fun updateView(newInfo: ChipbarInfo, currentView: ViewGroup) {
|
||||
updateGestureListening()
|
||||
|
||||
logger.logViewUpdate(
|
||||
newInfo.windowTitle,
|
||||
newInfo.text.loadText(context),
|
||||
@@ -228,6 +231,42 @@ constructor(
|
||||
includeMargins = true,
|
||||
onAnimationEnd,
|
||||
)
|
||||
|
||||
updateGestureListening()
|
||||
}
|
||||
|
||||
private fun updateGestureListening() {
|
||||
if (swipeChipbarAwayGestureHandler == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val currentDisplayInfo = getCurrentDisplayInfo()
|
||||
if (currentDisplayInfo != null && currentDisplayInfo.info.allowSwipeToDismiss) {
|
||||
swipeChipbarAwayGestureHandler.setViewFetcher { currentDisplayInfo.view }
|
||||
swipeChipbarAwayGestureHandler.addOnGestureDetectedCallback(TAG) {
|
||||
onSwipeUpGestureDetected()
|
||||
}
|
||||
} else {
|
||||
swipeChipbarAwayGestureHandler.resetViewFetcher()
|
||||
swipeChipbarAwayGestureHandler.removeOnGestureDetectedCallback(TAG)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onSwipeUpGestureDetected() {
|
||||
val currentDisplayInfo = getCurrentDisplayInfo()
|
||||
if (currentDisplayInfo == null) {
|
||||
logger.logSwipeGestureError(id = null, errorMsg = "No info is being displayed")
|
||||
return
|
||||
}
|
||||
if (!currentDisplayInfo.info.allowSwipeToDismiss) {
|
||||
logger.logSwipeGestureError(
|
||||
id = currentDisplayInfo.info.id,
|
||||
errorMsg = "This view prohibits swipe-to-dismiss",
|
||||
)
|
||||
return
|
||||
}
|
||||
removeView(currentDisplayInfo.info.id, SWIPE_UP_GESTURE_REASON)
|
||||
updateGestureListening()
|
||||
}
|
||||
|
||||
private fun ViewGroup.getInnerView(): ViewGroup {
|
||||
@@ -250,3 +289,5 @@ constructor(
|
||||
private const val ANIMATION_IN_DURATION = 500L
|
||||
private const val ANIMATION_OUT_DURATION = 250L
|
||||
@IdRes private val INFO_TAG = R.id.tag_chipbar_info
|
||||
private const val SWIPE_UP_GESTURE_REASON = "SWIPE_UP_GESTURE_DETECTED"
|
||||
private const val TAG = "ChipbarCoordinator"
|
||||
|
||||
@@ -33,12 +33,14 @@ import com.android.systemui.temporarydisplay.ViewPriority
|
||||
* @property endItem an optional end item to display at the end of the chipbar (on the right in LTR
|
||||
* locales; on the left in RTL locales).
|
||||
* @property vibrationEffect an optional vibration effect when the chipbar is displayed
|
||||
* @property allowSwipeToDismiss true if users are allowed to swipe up to dismiss this chipbar.
|
||||
*/
|
||||
data class ChipbarInfo(
|
||||
val startIcon: TintedIcon,
|
||||
val text: Text,
|
||||
val endItem: ChipbarEndItem?,
|
||||
val vibrationEffect: VibrationEffect? = null,
|
||||
val allowSwipeToDismiss: Boolean = false,
|
||||
override val windowTitle: String,
|
||||
override val wakeReason: String,
|
||||
override val timeoutMs: Int,
|
||||
|
||||
@@ -46,4 +46,16 @@ constructor(
|
||||
{ "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
|
||||
)
|
||||
}
|
||||
|
||||
fun logSwipeGestureError(id: String?, errorMsg: String) {
|
||||
buffer.log(
|
||||
tag,
|
||||
LogLevel.WARNING,
|
||||
{
|
||||
str1 = id
|
||||
str2 = errorMsg
|
||||
},
|
||||
{ "Chipbar swipe gesture detected for incorrect state. id=$str1 error=$str2" }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.temporarydisplay.chipbar
|
||||
|
||||
import android.content.Context
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import com.android.systemui.statusbar.gesture.SwipeUpGestureHandler
|
||||
import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
|
||||
import com.android.systemui.util.boundsOnScreen
|
||||
|
||||
/**
|
||||
* A class to detect when a user has swiped the chipbar away.
|
||||
*
|
||||
* Effectively [SysUISingleton]. But, this shouldn't be created if the gesture isn't enabled. See
|
||||
* [TemporaryDisplayModule.provideSwipeChipbarAwayGestureHandler].
|
||||
*/
|
||||
class SwipeChipbarAwayGestureHandler(
|
||||
context: Context,
|
||||
logger: SwipeUpGestureLogger,
|
||||
) : SwipeUpGestureHandler(context, logger, loggerTag = LOGGER_TAG) {
|
||||
|
||||
private var viewFetcher: () -> View? = { null }
|
||||
|
||||
override fun startOfGestureIsWithinBounds(ev: MotionEvent): Boolean {
|
||||
val view = viewFetcher.invoke() ?: return false
|
||||
// Since chipbar is in its own window, we need to use [boundsOnScreen] to get an accurate
|
||||
// bottom. ([view.bottom] would be relative to its window, which would be too small.)
|
||||
val viewBottom = view.boundsOnScreen.bottom
|
||||
// Allow the gesture to start a bit below the chipbar
|
||||
return ev.y <= 1.5 * viewBottom
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a fetcher that returns the current chipbar view. The fetcher will be invoked whenever a
|
||||
* gesture starts to determine if the gesture is near the chipbar.
|
||||
*/
|
||||
fun setViewFetcher(fetcher: () -> View?) {
|
||||
viewFetcher = fetcher
|
||||
}
|
||||
|
||||
/** Removes the current view fetcher. */
|
||||
fun resetViewFetcher() {
|
||||
viewFetcher = { null }
|
||||
}
|
||||
}
|
||||
|
||||
private const val LOGGER_TAG = "SwipeChipbarAway"
|
||||
@@ -16,22 +16,38 @@
|
||||
|
||||
package com.android.systemui.temporarydisplay.dagger
|
||||
|
||||
import android.content.Context
|
||||
import com.android.systemui.dagger.SysUISingleton
|
||||
import com.android.systemui.log.LogBufferFactory
|
||||
import com.android.systemui.media.taptotransfer.MediaTttFlags
|
||||
import com.android.systemui.plugins.log.LogBuffer
|
||||
import com.android.systemui.statusbar.gesture.SwipeUpGestureLogger
|
||||
import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
||||
@Module
|
||||
interface TemporaryDisplayModule {
|
||||
@Module
|
||||
companion object {
|
||||
@JvmStatic
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
@ChipbarLog
|
||||
fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
|
||||
return factory.create("ChipbarLog", 40)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@SysUISingleton
|
||||
fun provideSwipeChipbarAwayGestureHandler(
|
||||
mediaTttFlags: MediaTttFlags,
|
||||
context: Context,
|
||||
logger: SwipeUpGestureLogger,
|
||||
): SwipeChipbarAwayGestureHandler? {
|
||||
return if (mediaTttFlags.isMediaTttDismissGestureEnabled()) {
|
||||
SwipeChipbarAwayGestureHandler(context, logger)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarInfo
|
||||
import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
|
||||
import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
|
||||
import com.android.systemui.temporarydisplay.chipbar.SwipeChipbarAwayGestureHandler
|
||||
import com.android.systemui.util.concurrency.FakeExecutor
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
@@ -98,6 +99,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
|
||||
@Mock private lateinit var viewUtil: ViewUtil
|
||||
@Mock private lateinit var windowManager: WindowManager
|
||||
@Mock private lateinit var vibratorHelper: VibratorHelper
|
||||
@Mock private lateinit var swipeHandler: SwipeChipbarAwayGestureHandler
|
||||
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
|
||||
private lateinit var fakeWakeLock: WakeLockFake
|
||||
private lateinit var chipbarCoordinator: ChipbarCoordinator
|
||||
@@ -148,6 +150,7 @@ class MediaTttSenderCoordinatorTest : SysuiTestCase() {
|
||||
powerManager,
|
||||
falsingManager,
|
||||
falsingCollector,
|
||||
swipeHandler,
|
||||
viewUtil,
|
||||
vibratorHelper,
|
||||
fakeWakeLockBuilder,
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.os.PowerManager
|
||||
import android.os.VibrationEffect
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.WindowManager
|
||||
@@ -43,6 +44,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController
|
||||
import com.android.systemui.temporarydisplay.ViewPriority
|
||||
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.mockito.eq
|
||||
import com.android.systemui.util.time.FakeSystemClock
|
||||
import com.android.systemui.util.view.ViewUtil
|
||||
@@ -54,6 +57,7 @@ import org.junit.runner.RunWith
|
||||
import org.mockito.ArgumentCaptor
|
||||
import org.mockito.ArgumentMatchers.anyInt
|
||||
import org.mockito.Mock
|
||||
import org.mockito.Mockito.never
|
||||
import org.mockito.Mockito.verify
|
||||
import org.mockito.Mockito.`when` as whenever
|
||||
import org.mockito.MockitoAnnotations
|
||||
@@ -74,6 +78,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
||||
@Mock private lateinit var falsingCollector: FalsingCollector
|
||||
@Mock private lateinit var viewUtil: ViewUtil
|
||||
@Mock private lateinit var vibratorHelper: VibratorHelper
|
||||
@Mock private lateinit var swipeGestureHandler: SwipeChipbarAwayGestureHandler
|
||||
private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
|
||||
private lateinit var fakeWakeLock: WakeLockFake
|
||||
private lateinit var fakeClock: FakeSystemClock
|
||||
@@ -106,6 +111,7 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
||||
powerManager,
|
||||
falsingManager,
|
||||
falsingCollector,
|
||||
swipeGestureHandler,
|
||||
viewUtil,
|
||||
vibratorHelper,
|
||||
fakeWakeLockBuilder,
|
||||
@@ -430,17 +436,101 @@ class ChipbarCoordinatorTest : SysuiTestCase() {
|
||||
verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeToDismiss_false_neverListensForGesture() {
|
||||
underTest.displayView(
|
||||
createChipbarInfo(
|
||||
Icon.Resource(R.drawable.ic_cake, contentDescription = null),
|
||||
Text.Loaded("title text"),
|
||||
endItem = ChipbarEndItem.Loading,
|
||||
allowSwipeToDismiss = false,
|
||||
)
|
||||
)
|
||||
|
||||
verify(swipeGestureHandler, never()).addOnGestureDetectedCallback(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeToDismiss_true_listensForGesture() {
|
||||
underTest.displayView(
|
||||
createChipbarInfo(
|
||||
Icon.Resource(R.drawable.ic_cake, contentDescription = null),
|
||||
Text.Loaded("title text"),
|
||||
endItem = ChipbarEndItem.Loading,
|
||||
allowSwipeToDismiss = true,
|
||||
)
|
||||
)
|
||||
|
||||
verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeToDismiss_swipeOccurs_viewDismissed() {
|
||||
underTest.displayView(
|
||||
createChipbarInfo(
|
||||
Icon.Resource(R.drawable.ic_cake, contentDescription = null),
|
||||
Text.Loaded("title text"),
|
||||
endItem = ChipbarEndItem.Loading,
|
||||
allowSwipeToDismiss = true,
|
||||
)
|
||||
)
|
||||
val view = getChipbarView()
|
||||
|
||||
val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
|
||||
verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor))
|
||||
|
||||
callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0))
|
||||
|
||||
verify(windowManager).removeView(view)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun swipeToDismiss_viewUpdatedToFalse_swipeOccurs_viewNotDismissed() {
|
||||
underTest.displayView(
|
||||
createChipbarInfo(
|
||||
Icon.Resource(R.drawable.ic_cake, contentDescription = null),
|
||||
Text.Loaded("title text"),
|
||||
endItem = ChipbarEndItem.Loading,
|
||||
allowSwipeToDismiss = true,
|
||||
)
|
||||
)
|
||||
val view = getChipbarView()
|
||||
val callbackCaptor = argumentCaptor<(MotionEvent) -> Unit>()
|
||||
verify(swipeGestureHandler).addOnGestureDetectedCallback(any(), capture(callbackCaptor))
|
||||
|
||||
// WHEN the view is updated to not allow swipe-to-dismiss
|
||||
underTest.displayView(
|
||||
createChipbarInfo(
|
||||
Icon.Resource(R.drawable.ic_cake, contentDescription = null),
|
||||
Text.Loaded("title text"),
|
||||
endItem = ChipbarEndItem.Loading,
|
||||
allowSwipeToDismiss = false,
|
||||
)
|
||||
)
|
||||
|
||||
// THEN the callback is removed
|
||||
verify(swipeGestureHandler).removeOnGestureDetectedCallback(any())
|
||||
|
||||
// And WHEN the old callback is invoked
|
||||
callbackCaptor.value.invoke(MotionEvent.obtain(0L, 0L, 0, 0f, 0f, 0))
|
||||
|
||||
// THEN it is ignored and view isn't removed
|
||||
verify(windowManager, never()).removeView(view)
|
||||
}
|
||||
|
||||
private fun createChipbarInfo(
|
||||
startIcon: Icon,
|
||||
text: Text,
|
||||
endItem: ChipbarEndItem?,
|
||||
vibrationEffect: VibrationEffect? = null,
|
||||
allowSwipeToDismiss: Boolean = false,
|
||||
): ChipbarInfo {
|
||||
return ChipbarInfo(
|
||||
TintedIcon(startIcon, tintAttr = null),
|
||||
text,
|
||||
endItem,
|
||||
vibrationEffect,
|
||||
allowSwipeToDismiss,
|
||||
windowTitle = WINDOW_TITLE,
|
||||
wakeReason = WAKE_REASON,
|
||||
timeoutMs = TIMEOUT,
|
||||
|
||||
@@ -43,6 +43,7 @@ class FakeChipbarCoordinator(
|
||||
powerManager: PowerManager,
|
||||
falsingManager: FalsingManager,
|
||||
falsingCollector: FalsingCollector,
|
||||
swipeChipbarAwayGestureHandler: SwipeChipbarAwayGestureHandler,
|
||||
viewUtil: ViewUtil,
|
||||
vibratorHelper: VibratorHelper,
|
||||
wakeLockBuilder: WakeLock.Builder,
|
||||
@@ -59,6 +60,7 @@ class FakeChipbarCoordinator(
|
||||
powerManager,
|
||||
falsingManager,
|
||||
falsingCollector,
|
||||
swipeChipbarAwayGestureHandler,
|
||||
viewUtil,
|
||||
vibratorHelper,
|
||||
wakeLockBuilder,
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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.temporarydisplay.chipbar
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import androidx.test.filters.SmallTest
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.mock
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
@SmallTest
|
||||
class SwipeChipbarAwayGestureHandlerTest : SysuiTestCase() {
|
||||
|
||||
private lateinit var underTest: SwipeChipbarAwayGestureHandler
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
underTest = SwipeChipbarAwayGestureHandler(context, mock())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startOfGestureIsWithinBounds_noViewFetcher_returnsFalse() {
|
||||
assertThat(underTest.startOfGestureIsWithinBounds(createMotionEvent())).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startOfGestureIsWithinBounds_usesViewFetcher_aboveBottom_returnsTrue() {
|
||||
val view = createMockView()
|
||||
|
||||
underTest.setViewFetcher { view }
|
||||
|
||||
val motionEvent = createMotionEvent(y = VIEW_BOTTOM - 100f)
|
||||
assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startOfGestureIsWithinBounds_usesViewFetcher_slightlyBelowBottom_returnsTrue() {
|
||||
val view = createMockView()
|
||||
|
||||
underTest.setViewFetcher { view }
|
||||
|
||||
val motionEvent = createMotionEvent(y = VIEW_BOTTOM + 20f)
|
||||
assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startOfGestureIsWithinBounds_usesViewFetcher_tooFarDown_returnsFalse() {
|
||||
val view = createMockView()
|
||||
|
||||
underTest.setViewFetcher { view }
|
||||
|
||||
val motionEvent = createMotionEvent(y = VIEW_BOTTOM * 4f)
|
||||
assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startOfGestureIsWithinBounds_viewFetcherReset_returnsFalse() {
|
||||
val view = createMockView()
|
||||
|
||||
underTest.setViewFetcher { view }
|
||||
|
||||
val motionEvent = createMotionEvent(y = VIEW_BOTTOM - 100f)
|
||||
assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isTrue()
|
||||
|
||||
underTest.resetViewFetcher()
|
||||
assertThat(underTest.startOfGestureIsWithinBounds(motionEvent)).isFalse()
|
||||
}
|
||||
|
||||
private fun createMotionEvent(y: Float = 0f): MotionEvent {
|
||||
return MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0f, y, 0)
|
||||
}
|
||||
|
||||
private fun createMockView(): View {
|
||||
return mock<View>().also {
|
||||
doAnswer { invocation ->
|
||||
val out: Rect = invocation.getArgument(0)
|
||||
out.set(0, 0, 0, VIEW_BOTTOM)
|
||||
null
|
||||
}
|
||||
.whenever(it)
|
||||
.getBoundsOnScreen(any())
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val VIEW_BOTTOM = 455
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user