Loading packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt +3 −3 Original line number Diff line number Diff line Loading @@ -44,14 +44,14 @@ import org.mockito.junit.MockitoRule @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class SeekableSliderHapticPluginTest : SysuiTestCase() { class SeekbarHapticPluginTest : SysuiTestCase() { private val kosmos = Kosmos() @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var vibratorHelper: VibratorHelper private val seekBar = SeekBar(mContext) private lateinit var plugin: SeekableSliderHapticPlugin private lateinit var plugin: SeekbarHapticPlugin @Before fun setup() { Loading Loading @@ -142,7 +142,7 @@ class SeekableSliderHapticPluginTest : SysuiTestCase() { private fun createPlugin() { plugin = SeekableSliderHapticPlugin( SeekbarHapticPlugin( vibratorHelper, kosmos.fakeSystemClock, ) Loading packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderStateProducerTest.kt +134 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2024 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. Loading @@ -16,7 +16,6 @@ package com.android.systemui.haptics.slider import android.widget.SeekBar import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading @@ -28,17 +27,16 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SeekableSliderEventProducerTest : SysuiTestCase() { class SliderStateProducerTest : SysuiTestCase() { private val seekBar = SeekBar(mContext) private val eventProducer = SeekableSliderEventProducer() private val eventProducer = SliderStateProducer() private val eventFlow = eventProducer.produceEvents() @Test fun onStartTrackingTouch_noProgress_trackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onStartTrackingTouch(seekBar) eventProducer.onStartTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0F), latest) } Loading @@ -47,101 +45,90 @@ class SeekableSliderEventProducerTest : SysuiTestCase() { fun onStopTrackingTouch_noProgress_StoppedTrackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onStopTrackingTouch(seekBar) eventProducer.onStopTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0F), latest) } @Test fun onProgressChangeByUser_changeByUserEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onStartTrackingProgram_noProgress_trackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStartTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_PROGRAM, 0F), latest) } @Test fun onProgressChangeByUser_zeroWidthSlider_changeByUserEventProduced_withMaxProgress() = runTest { // No-width slider where the min and max values are the same seekBar.min = 100 seekBar.max = 100 val progress = 50 fun onStopTrackingProgram_noProgress_StoppedTrackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStopTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 1.0F), latest) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_PROGRAM, 0F), latest) } @Test fun onProgressChangeByProgram_changeByProgramEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onProgressChangeByUser_changeByUserEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, false) eventProducer.onProgressChanged(/*fromUser =*/ true, progress) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, progress), latest) } @Test fun onProgressChangeByProgram_zeroWidthSlider_changeByProgramEventProduced_withMaxProgress() = runTest { // No-width slider where the min and max values are the same seekBar.min = 100 seekBar.max = 100 val progress = 50 fun onProgressChangeByProgram_changeByProgramEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, false) eventProducer.onProgressChanged(/*fromUser =*/ false, progress) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 1.0F), latest) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress), latest) } @Test fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStartTrackingTouch(seekBar) eventProducer.onProgressChanged(/*fromUser =*/ true, progress) eventProducer.onStartTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress), latest) } @Test fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStopTrackingTouch(seekBar) eventProducer.onProgressChanged(/*fromUser =*/ true, progress) eventProducer.onStopTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, progress), latest) } @Test fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest { fun onStartTrackingProgram_afterProgress_trackingProgramEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onStartTrackingTouch(seekBar) eventProducer.onArrowUp() eventProducer.onProgressChanged(/*fromUser =*/ false, progress) eventProducer.onStartTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_PROGRAM, progress), latest) } @Test fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest { val progress = 50 fun onStopTrackingProgram_afterProgress_stopTrackingProgramEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, false) eventProducer.onArrowUp() eventProducer.onProgressChanged(/*fromUser =*/ false, progress) eventProducer.onStopTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_PROGRAM, progress), latest) } } packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt +2 −2 Original line number Diff line number Diff line Loading @@ -23,11 +23,11 @@ import kotlinx.coroutines.awaitCancellation object HapticSliderViewBinder { /** * Binds a [SeekableSliderHapticPlugin] to a [View]. The binded view should be a * Binds a [SeekbarHapticPlugin] to a [View]. The binded view should be a * [android.widget.SeekBar] or a container of a [android.widget.SeekBar] */ @JvmStatic fun bind(view: View?, plugin: SeekableSliderHapticPlugin) { fun bind(view: View?, plugin: SeekbarHapticPlugin) { view?.repeatWhenAttached { plugin.startInScope(lifecycleScope) try { Loading packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt→packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt +38 −14 Original line number Diff line number Diff line Loading @@ -30,12 +30,12 @@ import kotlinx.coroutines.launch /** * A plugin added to a manager of a [android.widget.SeekBar] that adds dynamic haptic feedback. * * A [SeekableSliderEventProducer] is used as the producer of slider events, a * A [SliderStateProducer] is used as the producer of slider events, a * [SliderHapticFeedbackProvider] is used as the listener of slider states to play haptic feedback * depending on the state, and a [SeekableSliderTracker] is used as the state machine handler that * depending on the state, and a [SliderStateTracker] is used as the state machine handler that * tracks and manipulates the slider state. */ class SeekableSliderHapticPlugin class SeekbarHapticPlugin @JvmOverloads constructor( vibratorHelper: VibratorHelper, Loading @@ -46,7 +46,7 @@ constructor( private val velocityTracker = VelocityTracker.obtain() private val sliderEventProducer = SeekableSliderEventProducer() private val sliderEventProducer = SliderStateProducer() private val sliderHapticFeedbackProvider = SliderHapticFeedbackProvider( Loading @@ -56,7 +56,7 @@ constructor( systemClock, ) private var sliderTracker: SeekableSliderTracker? = null private var sliderTracker: SliderStateTracker? = null private var pluginScope: CoroutineScope? = null Loading Loading @@ -86,7 +86,7 @@ constructor( fun startInScope(scope: CoroutineScope) { if (sliderTracker != null) stop() sliderTracker = SeekableSliderTracker( SliderStateTracker( sliderHapticFeedbackProvider, sliderEventProducer, scope, Loading Loading @@ -116,28 +116,52 @@ constructor( /** onStartTrackingTouch event from the slider's [android.widget.SeekBar] */ fun onStartTrackingTouch(seekBar: SeekBar) { if (isTracking) { sliderEventProducer.onStartTrackingTouch(seekBar) sliderEventProducer.onStartTracking(true) } } /** onProgressChanged event from the slider's [android.widget.SeekBar] */ fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (isTracking) { sliderEventProducer.onProgressChanged(seekBar, progress, fromUser) if (sliderTracker?.currentState == SliderState.IDLE && !fromUser) { // This case translates to the slider starting to track program changes sliderEventProducer.resetWithProgress(normalizeProgress(seekBar, progress)) sliderEventProducer.onStartTracking(false) } else { sliderEventProducer.onProgressChanged( fromUser, normalizeProgress(seekBar, progress), ) } } } /** * Normalize the integer progress of a SeekBar to the range from 0F to 1F. * * @param[seekBar] The SeekBar that reports a progress. * @param[progress] The integer progress of the SeekBar within its min and max values. * @return The progress in the range from 0F to 1F. */ private fun normalizeProgress(seekBar: SeekBar, progress: Int): Float { if (seekBar.max == seekBar.min) { return 1.0f } val range = seekBar.max - seekBar.min return (progress - seekBar.min) / range.toFloat() } /** onStopTrackingTouch event from the slider's [android.widget.SeekBar] */ fun onStopTrackingTouch(seekBar: SeekBar) { if (isTracking) { sliderEventProducer.onStopTrackingTouch(seekBar) sliderEventProducer.onStopTracking(true) } } /** onArrowUp event recorded */ fun onArrowUp() { /** Programmatic changes have stopped */ private fun onStoppedTrackingProgram() { if (isTracking) { sliderEventProducer.onArrowUp() sliderEventProducer.onStopTracking(false) } } Loading @@ -146,7 +170,7 @@ constructor( * * This event is used to estimate the key-up event based on a running a timer as a waiting * coroutine in the [pluginScope]. A key-up event in a slider corresponds to an onArrowUp event. * Therefore, [onArrowUp] must be called after the timeout. * Therefore, [onStoppedTrackingProgram] must be called after the timeout. */ fun onKeyDown() { if (!isTracking) return Loading @@ -158,7 +182,7 @@ constructor( keyUpJob = pluginScope?.launch { delay(KEY_UP_TIMEOUT) onArrowUp() onStoppedTrackingProgram() } } Loading packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt +3 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ enum class SliderEventType { NOTHING, /* The slider has captured a touch input and is tracking touch events. */ STARTED_TRACKING_TOUCH, /* The slider started tracking programmatic value changes */ STARTED_TRACKING_PROGRAM, /* The slider progress is changing due to user touch input. */ PROGRESS_CHANGE_BY_USER, /* The slider progress is changing programmatically. */ Loading @@ -29,5 +31,5 @@ enum class SliderEventType { /* The slider has stopped tracking touch events. */ STOPPED_TRACKING_TOUCH, /* The external (not touch) stimulus that was modifying the slider progress has stopped. */ ARROW_UP, STOPPED_TRACKING_PROGRAM, } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekableSliderHapticPluginTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SeekbarHapticPluginTest.kt +3 −3 Original line number Diff line number Diff line Loading @@ -44,14 +44,14 @@ import org.mockito.junit.MockitoRule @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) class SeekableSliderHapticPluginTest : SysuiTestCase() { class SeekbarHapticPluginTest : SysuiTestCase() { private val kosmos = Kosmos() @Rule @JvmField val mMockitoRule: MockitoRule = MockitoJUnit.rule() @Mock private lateinit var vibratorHelper: VibratorHelper private val seekBar = SeekBar(mContext) private lateinit var plugin: SeekableSliderHapticPlugin private lateinit var plugin: SeekbarHapticPlugin @Before fun setup() { Loading Loading @@ -142,7 +142,7 @@ class SeekableSliderHapticPluginTest : SysuiTestCase() { private fun createPlugin() { plugin = SeekableSliderHapticPlugin( SeekbarHapticPlugin( vibratorHelper, kosmos.fakeSystemClock, ) Loading
packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SeekableSliderEventProducerTest.kt→packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/slider/SliderStateProducerTest.kt +134 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 The Android Open Source Project * Copyright (C) 2024 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. Loading @@ -16,7 +16,6 @@ package com.android.systemui.haptics.slider import android.widget.SeekBar import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase Loading @@ -28,17 +27,16 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidJUnit4::class) class SeekableSliderEventProducerTest : SysuiTestCase() { class SliderStateProducerTest : SysuiTestCase() { private val seekBar = SeekBar(mContext) private val eventProducer = SeekableSliderEventProducer() private val eventProducer = SliderStateProducer() private val eventFlow = eventProducer.produceEvents() @Test fun onStartTrackingTouch_noProgress_trackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onStartTrackingTouch(seekBar) eventProducer.onStartTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0F), latest) } Loading @@ -47,101 +45,90 @@ class SeekableSliderEventProducerTest : SysuiTestCase() { fun onStopTrackingTouch_noProgress_StoppedTrackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onStopTrackingTouch(seekBar) eventProducer.onStopTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0F), latest) } @Test fun onProgressChangeByUser_changeByUserEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onStartTrackingProgram_noProgress_trackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStartTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_PROGRAM, 0F), latest) } @Test fun onProgressChangeByUser_zeroWidthSlider_changeByUserEventProduced_withMaxProgress() = runTest { // No-width slider where the min and max values are the same seekBar.min = 100 seekBar.max = 100 val progress = 50 fun onStopTrackingProgram_noProgress_StoppedTrackingTouchEventProduced() = runTest { val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStopTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, 1.0F), latest) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_PROGRAM, 0F), latest) } @Test fun onProgressChangeByProgram_changeByProgramEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onProgressChangeByUser_changeByUserEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, false) eventProducer.onProgressChanged(/*fromUser =*/ true, progress) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_USER, progress), latest) } @Test fun onProgressChangeByProgram_zeroWidthSlider_changeByProgramEventProduced_withMaxProgress() = runTest { // No-width slider where the min and max values are the same seekBar.min = 100 seekBar.max = 100 val progress = 50 fun onProgressChangeByProgram_changeByProgramEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, false) eventProducer.onProgressChanged(/*fromUser =*/ false, progress) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, 1.0F), latest) assertEquals(SliderEvent(SliderEventType.PROGRESS_CHANGE_BY_PROGRAM, progress), latest) } @Test fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onStartTrackingTouch_afterProgress_trackingTouchEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStartTrackingTouch(seekBar) eventProducer.onProgressChanged(/*fromUser =*/ true, progress) eventProducer.onStartTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_TOUCH, progress), latest) } @Test fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced_withNormalizedProgress() = runTest { val progress = 50 fun onStopTrackingTouch_afterProgress_stopTrackingTouchEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, true) eventProducer.onStopTrackingTouch(seekBar) eventProducer.onProgressChanged(/*fromUser =*/ true, progress) eventProducer.onStopTracking(/*fromUser =*/ true) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, 0.5F), latest) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_TOUCH, progress), latest) } @Test fun onArrowUp_afterStartTrackingTouch_ArrowUpProduced() = runTest { fun onStartTrackingProgram_afterProgress_trackingProgramEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onStartTrackingTouch(seekBar) eventProducer.onArrowUp() eventProducer.onProgressChanged(/*fromUser =*/ false, progress) eventProducer.onStartTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0f), latest) assertEquals(SliderEvent(SliderEventType.STARTED_TRACKING_PROGRAM, progress), latest) } @Test fun onArrowUp_afterChangeByProgram_ArrowUpProduced_withProgress() = runTest { val progress = 50 fun onStopTrackingProgram_afterProgress_stopTrackingProgramEventProduced() = runTest { val progress = 0.5f val latest by collectLastValue(eventFlow) eventProducer.onProgressChanged(seekBar, progress, false) eventProducer.onArrowUp() eventProducer.onProgressChanged(/*fromUser =*/ false, progress) eventProducer.onStopTracking(/*fromUser =*/ false) assertEquals(SliderEvent(SliderEventType.ARROW_UP, 0.5f), latest) assertEquals(SliderEvent(SliderEventType.STOPPED_TRACKING_PROGRAM, progress), latest) } }
packages/SystemUI/src/com/android/systemui/haptics/slider/HapticSliderViewBinder.kt +2 −2 Original line number Diff line number Diff line Loading @@ -23,11 +23,11 @@ import kotlinx.coroutines.awaitCancellation object HapticSliderViewBinder { /** * Binds a [SeekableSliderHapticPlugin] to a [View]. The binded view should be a * Binds a [SeekbarHapticPlugin] to a [View]. The binded view should be a * [android.widget.SeekBar] or a container of a [android.widget.SeekBar] */ @JvmStatic fun bind(view: View?, plugin: SeekableSliderHapticPlugin) { fun bind(view: View?, plugin: SeekbarHapticPlugin) { view?.repeatWhenAttached { plugin.startInScope(lifecycleScope) try { Loading
packages/SystemUI/src/com/android/systemui/haptics/slider/SeekableSliderHapticPlugin.kt→packages/SystemUI/src/com/android/systemui/haptics/slider/SeekbarHapticPlugin.kt +38 −14 Original line number Diff line number Diff line Loading @@ -30,12 +30,12 @@ import kotlinx.coroutines.launch /** * A plugin added to a manager of a [android.widget.SeekBar] that adds dynamic haptic feedback. * * A [SeekableSliderEventProducer] is used as the producer of slider events, a * A [SliderStateProducer] is used as the producer of slider events, a * [SliderHapticFeedbackProvider] is used as the listener of slider states to play haptic feedback * depending on the state, and a [SeekableSliderTracker] is used as the state machine handler that * depending on the state, and a [SliderStateTracker] is used as the state machine handler that * tracks and manipulates the slider state. */ class SeekableSliderHapticPlugin class SeekbarHapticPlugin @JvmOverloads constructor( vibratorHelper: VibratorHelper, Loading @@ -46,7 +46,7 @@ constructor( private val velocityTracker = VelocityTracker.obtain() private val sliderEventProducer = SeekableSliderEventProducer() private val sliderEventProducer = SliderStateProducer() private val sliderHapticFeedbackProvider = SliderHapticFeedbackProvider( Loading @@ -56,7 +56,7 @@ constructor( systemClock, ) private var sliderTracker: SeekableSliderTracker? = null private var sliderTracker: SliderStateTracker? = null private var pluginScope: CoroutineScope? = null Loading Loading @@ -86,7 +86,7 @@ constructor( fun startInScope(scope: CoroutineScope) { if (sliderTracker != null) stop() sliderTracker = SeekableSliderTracker( SliderStateTracker( sliderHapticFeedbackProvider, sliderEventProducer, scope, Loading Loading @@ -116,28 +116,52 @@ constructor( /** onStartTrackingTouch event from the slider's [android.widget.SeekBar] */ fun onStartTrackingTouch(seekBar: SeekBar) { if (isTracking) { sliderEventProducer.onStartTrackingTouch(seekBar) sliderEventProducer.onStartTracking(true) } } /** onProgressChanged event from the slider's [android.widget.SeekBar] */ fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { if (isTracking) { sliderEventProducer.onProgressChanged(seekBar, progress, fromUser) if (sliderTracker?.currentState == SliderState.IDLE && !fromUser) { // This case translates to the slider starting to track program changes sliderEventProducer.resetWithProgress(normalizeProgress(seekBar, progress)) sliderEventProducer.onStartTracking(false) } else { sliderEventProducer.onProgressChanged( fromUser, normalizeProgress(seekBar, progress), ) } } } /** * Normalize the integer progress of a SeekBar to the range from 0F to 1F. * * @param[seekBar] The SeekBar that reports a progress. * @param[progress] The integer progress of the SeekBar within its min and max values. * @return The progress in the range from 0F to 1F. */ private fun normalizeProgress(seekBar: SeekBar, progress: Int): Float { if (seekBar.max == seekBar.min) { return 1.0f } val range = seekBar.max - seekBar.min return (progress - seekBar.min) / range.toFloat() } /** onStopTrackingTouch event from the slider's [android.widget.SeekBar] */ fun onStopTrackingTouch(seekBar: SeekBar) { if (isTracking) { sliderEventProducer.onStopTrackingTouch(seekBar) sliderEventProducer.onStopTracking(true) } } /** onArrowUp event recorded */ fun onArrowUp() { /** Programmatic changes have stopped */ private fun onStoppedTrackingProgram() { if (isTracking) { sliderEventProducer.onArrowUp() sliderEventProducer.onStopTracking(false) } } Loading @@ -146,7 +170,7 @@ constructor( * * This event is used to estimate the key-up event based on a running a timer as a waiting * coroutine in the [pluginScope]. A key-up event in a slider corresponds to an onArrowUp event. * Therefore, [onArrowUp] must be called after the timeout. * Therefore, [onStoppedTrackingProgram] must be called after the timeout. */ fun onKeyDown() { if (!isTracking) return Loading @@ -158,7 +182,7 @@ constructor( keyUpJob = pluginScope?.launch { delay(KEY_UP_TIMEOUT) onArrowUp() onStoppedTrackingProgram() } } Loading
packages/SystemUI/src/com/android/systemui/haptics/slider/SliderEventType.kt +3 −1 Original line number Diff line number Diff line Loading @@ -22,6 +22,8 @@ enum class SliderEventType { NOTHING, /* The slider has captured a touch input and is tracking touch events. */ STARTED_TRACKING_TOUCH, /* The slider started tracking programmatic value changes */ STARTED_TRACKING_PROGRAM, /* The slider progress is changing due to user touch input. */ PROGRESS_CHANGE_BY_USER, /* The slider progress is changing programmatically. */ Loading @@ -29,5 +31,5 @@ enum class SliderEventType { /* The slider has stopped tracking touch events. */ STOPPED_TRACKING_TOUCH, /* The external (not touch) stimulus that was modifying the slider progress has stopped. */ ARROW_UP, STOPPED_TRACKING_PROGRAM, }