Loading packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +36 −8 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.media import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.os.SystemClock import android.view.MotionEvent import android.view.View import android.widget.SeekBar Loading @@ -31,6 +32,38 @@ import com.android.systemui.util.concurrency.DelayableExecutor private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L private fun PlaybackState.isInMotion(): Boolean { return this.state == PlaybackState.STATE_PLAYING || this.state == PlaybackState.STATE_FAST_FORWARDING || this.state == PlaybackState.STATE_REWINDING } /** * Gets the playback position while accounting for the time since the [PlaybackState] was last * retrieved. * * This method closely follows the implementation of * [MediaSessionRecord#getStateWithUpdatedPosition]. */ private fun PlaybackState.computePosition(duration: Long): Long { var currentPosition = this.position if (this.isInMotion()) { val updateTime = this.getLastPositionUpdateTime() val currentTime = SystemClock.elapsedRealtime() if (updateTime > 0) { var position = (this.playbackSpeed * (currentTime - updateTime)).toLong() + this.getPosition() if (duration >= 0 && position > duration) { position = duration.toLong() } else if (position < 0) { position = 0 } currentPosition = position } } return currentPosition } /** ViewModel for seek bar in QS media player. */ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { Loading Loading @@ -98,7 +131,8 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val currentPosition = controller?.playbackState?.position?.toInt() val duration = _data?.duration ?: -1 val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt() if (currentPosition != null && _data.elapsedTime != currentPosition) { _data = _data.copy(elapsedTime = currentPosition) } Loading @@ -109,13 +143,7 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { @WorkerThread private fun shouldPollPlaybackPosition(): Boolean { val state = playbackState?.state val moving = if (state == null) false else state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_BUFFERING || state == PlaybackState.STATE_FAST_FORWARDING || state == PlaybackState.STATE_REWINDING return moving && listening return listening && playbackState?.isInMotion() ?: false } /** Gets a listener to attach to the seek bar to handle seeking. */ Loading packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +5 −12 Original line number Diff line number Diff line Loading @@ -362,28 +362,21 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun taskUpdatesProgress() { // GIVEN that the PlaybackState contins the current position val position = 200L // GIVEN that the PlaybackState contins the initial position val initialPosition = 0L val state = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, position, 1f) setState(PlaybackState.STATE_PLAYING, initialPosition, 1f) build() } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController, Color.RED) // AND the playback state advances val nextPosition = 300L val nextState = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, nextPosition, 1f) build() } whenever(mockController.getPlaybackState()).thenReturn(nextState) // WHEN the task runs with(fakeExecutor) { advanceClockToNext() runAllReady() } // THEN elapsed time is captured assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(nextPosition.toInt()) // THEN elapsed time has increased assertThat(viewModel.progress.value!!.elapsedTime).isGreaterThan(initialPosition.toInt()) } @Test Loading Loading
packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +36 −8 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package com.android.systemui.media import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.PlaybackState import android.os.SystemClock import android.view.MotionEvent import android.view.View import android.widget.SeekBar Loading @@ -31,6 +32,38 @@ import com.android.systemui.util.concurrency.DelayableExecutor private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L private fun PlaybackState.isInMotion(): Boolean { return this.state == PlaybackState.STATE_PLAYING || this.state == PlaybackState.STATE_FAST_FORWARDING || this.state == PlaybackState.STATE_REWINDING } /** * Gets the playback position while accounting for the time since the [PlaybackState] was last * retrieved. * * This method closely follows the implementation of * [MediaSessionRecord#getStateWithUpdatedPosition]. */ private fun PlaybackState.computePosition(duration: Long): Long { var currentPosition = this.position if (this.isInMotion()) { val updateTime = this.getLastPositionUpdateTime() val currentTime = SystemClock.elapsedRealtime() if (updateTime > 0) { var position = (this.playbackSpeed * (currentTime - updateTime)).toLong() + this.getPosition() if (duration >= 0 && position > duration) { position = duration.toLong() } else if (position < 0) { position = 0 } currentPosition = position } } return currentPosition } /** ViewModel for seek bar in QS media player. */ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { Loading Loading @@ -98,7 +131,8 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val currentPosition = controller?.playbackState?.position?.toInt() val duration = _data?.duration ?: -1 val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt() if (currentPosition != null && _data.elapsedTime != currentPosition) { _data = _data.copy(elapsedTime = currentPosition) } Loading @@ -109,13 +143,7 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { @WorkerThread private fun shouldPollPlaybackPosition(): Boolean { val state = playbackState?.state val moving = if (state == null) false else state == PlaybackState.STATE_PLAYING || state == PlaybackState.STATE_BUFFERING || state == PlaybackState.STATE_FAST_FORWARDING || state == PlaybackState.STATE_REWINDING return moving && listening return listening && playbackState?.isInMotion() ?: false } /** Gets a listener to attach to the seek bar to handle seeking. */ Loading
packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +5 −12 Original line number Diff line number Diff line Loading @@ -362,28 +362,21 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Test fun taskUpdatesProgress() { // GIVEN that the PlaybackState contins the current position val position = 200L // GIVEN that the PlaybackState contins the initial position val initialPosition = 0L val state = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, position, 1f) setState(PlaybackState.STATE_PLAYING, initialPosition, 1f) build() } whenever(mockController.getPlaybackState()).thenReturn(state) viewModel.updateController(mockController, Color.RED) // AND the playback state advances val nextPosition = 300L val nextState = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, nextPosition, 1f) build() } whenever(mockController.getPlaybackState()).thenReturn(nextState) // WHEN the task runs with(fakeExecutor) { advanceClockToNext() runAllReady() } // THEN elapsed time is captured assertThat(viewModel.progress.value!!.elapsedTime).isEqualTo(nextPosition.toInt()) // THEN elapsed time has increased assertThat(viewModel.progress.value!!.elapsedTime).isGreaterThan(initialPosition.toInt()) } @Test Loading