Loading packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt +28 −1 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.view.View import android.view.ViewConfiguration import android.widget.SeekBar import androidx.annotation.AnyThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import androidx.core.view.GestureDetectorCompat import androidx.lifecycle.LiveData Loading @@ -37,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.util.concurrency.RepeatableExecutor import javax.inject.Inject import kotlin.math.abs private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10 Loading Loading @@ -325,6 +327,10 @@ constructor( return SeekBarChangeListener(this, falsingManager) } /** first and last motion events of seekbar grab. */ @VisibleForTesting var firstMotionEvent: MotionEvent? = null @VisibleForTesting var lastMotionEvent: MotionEvent? = null /** Attach touch handlers to the seek bar view. */ fun attachTouchHandlers(bar: SeekBar) { bar.setOnSeekBarChangeListener(seekBarListener) Loading @@ -351,6 +357,23 @@ constructor( } } /** * This method specifies if user made a bad seekbar grab or not. If the vertical distance from * first touch on seekbar is more than the horizontal distance, this means that the seekbar grab * is more vertical and should be rejected. Seekbar accepts horizontal grabs only. * * Single tap has the same first and last motion event, it is counted as a valid grab. * * @return whether the touch on seekbar is valid. */ private fun isValidSeekbarGrab(): Boolean { if (firstMotionEvent == null || lastMotionEvent == null) { return true } return abs(firstMotionEvent!!.x - lastMotionEvent!!.x) >= abs(firstMotionEvent!!.y - lastMotionEvent!!.y) } /** Listener interface to be notified when the user starts or stops scrubbing. */ interface ScrubbingChangeListener { fun onScrubbingChanged(scrubbing: Boolean) Loading @@ -376,7 +399,7 @@ constructor( } override fun onStopTrackingTouch(bar: SeekBar) { if (falsingManager.isFalseTouch(MEDIA_SEEKBAR)) { if (!viewModel.isValidSeekbarGrab() || falsingManager.isFalseTouch(MEDIA_SEEKBAR)) { viewModel.onSeekFalse() } viewModel.onSeek(bar.progress.toLong()) Loading Loading @@ -424,6 +447,8 @@ constructor( return false } detector.onTouchEvent(event) // Store the last motion event done on seekbar. viewModel.lastMotionEvent = event.copy() return !shouldGoToSeekBar } Loading Loading @@ -468,6 +493,8 @@ constructor( if (shouldGoToSeekBar) { bar.parent?.requestDisallowInterceptTouchEvent(true) } // Store the first motion event done on seekbar. viewModel.firstMotionEvent = event.copy() return shouldGoToSeekBar } Loading packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt +48 −5 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.MotionEvent import android.widget.SeekBar import androidx.arch.core.executor.ArchTaskExecutor import androidx.arch.core.executor.TaskExecutor Loading Loading @@ -466,20 +467,62 @@ public class SeekBarViewModelTest : SysuiTestCase() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true) whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) viewModel.updateController(mockController) val pos = 169 val pos = 40 val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(bar) onStopTrackingTouch(bar) } fakeExecutor.runAllReady() // THEN transport controls should not be used verify(mockTransport, never()).seekTo(pos.toLong()) } @Test fun onSeekbarGrabInvalidTouch() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.firstMotionEvent = MotionEvent.obtain(12L, 13L, MotionEvent.ACTION_DOWN, 76F, 0F, 0) viewModel.lastMotionEvent = MotionEvent.obtain(12L, 14L, MotionEvent.ACTION_UP, 78F, 4F, 0) val pos = 78 viewModel.attachTouchHandlers(mockBar) viewModel.updateController(mockController) // WHEN user ends drag val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(mockBar) onProgressChanged(mockBar, pos, true) onStopTrackingTouch(mockBar) onStartTrackingTouch(bar) onStopTrackingTouch(bar) } fakeExecutor.runAllReady() // THEN transport controls should not be used verify(mockTransport, never()).seekTo(pos.toLong()) } @Test fun onSeekbarGrabValidTouch() { whenever(mockController.transportControls).thenReturn(mockTransport) viewModel.firstMotionEvent = MotionEvent.obtain(12L, 13L, MotionEvent.ACTION_DOWN, 36F, 0F, 0) viewModel.lastMotionEvent = MotionEvent.obtain(12L, 14L, MotionEvent.ACTION_UP, 40F, 1F, 0) val pos = 40 viewModel.updateController(mockController) // WHEN user ends drag val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(bar) onStopTrackingTouch(bar) } fakeExecutor.runAllReady() // THEN transport controls should be used verify(mockTransport).seekTo(pos.toLong()) } @Test fun queuePollTaskWhenPlaying() { // GIVEN that the track is playing Loading Loading
packages/SystemUI/src/com/android/systemui/media/controls/models/player/SeekBarViewModel.kt +28 −1 Original line number Diff line number Diff line Loading @@ -27,6 +27,7 @@ import android.view.View import android.view.ViewConfiguration import android.widget.SeekBar import androidx.annotation.AnyThread import androidx.annotation.VisibleForTesting import androidx.annotation.WorkerThread import androidx.core.view.GestureDetectorCompat import androidx.lifecycle.LiveData Loading @@ -37,6 +38,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.statusbar.NotificationMediaManager import com.android.systemui.util.concurrency.RepeatableExecutor import javax.inject.Inject import kotlin.math.abs private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L private const val MIN_FLING_VELOCITY_SCALE_FACTOR = 10 Loading Loading @@ -325,6 +327,10 @@ constructor( return SeekBarChangeListener(this, falsingManager) } /** first and last motion events of seekbar grab. */ @VisibleForTesting var firstMotionEvent: MotionEvent? = null @VisibleForTesting var lastMotionEvent: MotionEvent? = null /** Attach touch handlers to the seek bar view. */ fun attachTouchHandlers(bar: SeekBar) { bar.setOnSeekBarChangeListener(seekBarListener) Loading @@ -351,6 +357,23 @@ constructor( } } /** * This method specifies if user made a bad seekbar grab or not. If the vertical distance from * first touch on seekbar is more than the horizontal distance, this means that the seekbar grab * is more vertical and should be rejected. Seekbar accepts horizontal grabs only. * * Single tap has the same first and last motion event, it is counted as a valid grab. * * @return whether the touch on seekbar is valid. */ private fun isValidSeekbarGrab(): Boolean { if (firstMotionEvent == null || lastMotionEvent == null) { return true } return abs(firstMotionEvent!!.x - lastMotionEvent!!.x) >= abs(firstMotionEvent!!.y - lastMotionEvent!!.y) } /** Listener interface to be notified when the user starts or stops scrubbing. */ interface ScrubbingChangeListener { fun onScrubbingChanged(scrubbing: Boolean) Loading @@ -376,7 +399,7 @@ constructor( } override fun onStopTrackingTouch(bar: SeekBar) { if (falsingManager.isFalseTouch(MEDIA_SEEKBAR)) { if (!viewModel.isValidSeekbarGrab() || falsingManager.isFalseTouch(MEDIA_SEEKBAR)) { viewModel.onSeekFalse() } viewModel.onSeek(bar.progress.toLong()) Loading Loading @@ -424,6 +447,8 @@ constructor( return false } detector.onTouchEvent(event) // Store the last motion event done on seekbar. viewModel.lastMotionEvent = event.copy() return !shouldGoToSeekBar } Loading Loading @@ -468,6 +493,8 @@ constructor( if (shouldGoToSeekBar) { bar.parent?.requestDisallowInterceptTouchEvent(true) } // Store the first motion event done on seekbar. viewModel.firstMotionEvent = event.copy() return shouldGoToSeekBar } Loading
packages/SystemUI/tests/src/com/android/systemui/media/controls/models/player/SeekBarViewModelTest.kt +48 −5 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.MotionEvent import android.widget.SeekBar import androidx.arch.core.executor.ArchTaskExecutor import androidx.arch.core.executor.TaskExecutor Loading Loading @@ -466,20 +467,62 @@ public class SeekBarViewModelTest : SysuiTestCase() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) whenever(falsingManager.isFalseTouch(Classifier.MEDIA_SEEKBAR)).thenReturn(true) whenever(falsingManager.isFalseTap(anyInt())).thenReturn(true) viewModel.updateController(mockController) val pos = 169 val pos = 40 val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(bar) onStopTrackingTouch(bar) } fakeExecutor.runAllReady() // THEN transport controls should not be used verify(mockTransport, never()).seekTo(pos.toLong()) } @Test fun onSeekbarGrabInvalidTouch() { whenever(mockController.getTransportControls()).thenReturn(mockTransport) viewModel.firstMotionEvent = MotionEvent.obtain(12L, 13L, MotionEvent.ACTION_DOWN, 76F, 0F, 0) viewModel.lastMotionEvent = MotionEvent.obtain(12L, 14L, MotionEvent.ACTION_UP, 78F, 4F, 0) val pos = 78 viewModel.attachTouchHandlers(mockBar) viewModel.updateController(mockController) // WHEN user ends drag val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(mockBar) onProgressChanged(mockBar, pos, true) onStopTrackingTouch(mockBar) onStartTrackingTouch(bar) onStopTrackingTouch(bar) } fakeExecutor.runAllReady() // THEN transport controls should not be used verify(mockTransport, never()).seekTo(pos.toLong()) } @Test fun onSeekbarGrabValidTouch() { whenever(mockController.transportControls).thenReturn(mockTransport) viewModel.firstMotionEvent = MotionEvent.obtain(12L, 13L, MotionEvent.ACTION_DOWN, 36F, 0F, 0) viewModel.lastMotionEvent = MotionEvent.obtain(12L, 14L, MotionEvent.ACTION_UP, 40F, 1F, 0) val pos = 40 viewModel.updateController(mockController) // WHEN user ends drag val bar = SeekBar(context).apply { progress = pos } with(viewModel.seekBarListener) { onStartTrackingTouch(bar) onStopTrackingTouch(bar) } fakeExecutor.runAllReady() // THEN transport controls should be used verify(mockTransport).seekTo(pos.toLong()) } @Test fun queuePollTaskWhenPlaying() { // GIVEN that the track is playing Loading