Loading packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +1 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,7 @@ public class MediaControlPanel { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } mSeekBarViewModel.onDestroy(); } private void loadDimens() { Loading packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +30 −1 Original line number Diff line number Diff line Loading @@ -78,7 +78,22 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { val progress: LiveData<Progress> get() = _progress private var controller: MediaController? = null set(value) { if (field?.sessionToken != value?.sessionToken) { field?.unregisterCallback(callback) value?.registerCallback(callback) field = value } } private var playbackState: PlaybackState? = null private var callback = object : MediaController.Callback() { override fun onPlaybackStateChanged(state: PlaybackState) { playbackState = state if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } } } /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true Loading @@ -95,6 +110,9 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { @WorkerThread fun onSeek(position: Long) { controller?.transportControls?.seekTo(position) // Invalidate the cached playbackState to avoid the thumb jumping back to the previous // position. playbackState = null } /** Loading Loading @@ -125,12 +143,23 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { */ @AnyThread fun clearController() = bgExecutor.execute { controller = null playbackState = null _data = _data.copy(enabled = false) } /** * Call to clean up any resources. */ @AnyThread fun onDestroy() { controller = null playbackState = null } @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val duration = _data?.duration ?: -1 val duration = _data.duration ?: -1 val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt() if (currentPosition != null && _data.elapsedTime != currentPosition) { _data = _data.copy(elapsedTime = currentPosition) Loading packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +79 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.media import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper Loading @@ -35,9 +36,12 @@ import org.junit.After 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.any import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever Loading @@ -61,12 +65,15 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Mock private lateinit var mockController: MediaController @Mock private lateinit var mockTransport: MediaController.TransportControls private val token1 = MediaSession.Token(1, null) private val token2 = MediaSession.Token(2, null) @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) viewModel = SeekBarViewModel(fakeExecutor) mockController = mock(MediaController::class.java) whenever(mockController.sessionToken).thenReturn(token1) mockTransport = mock(MediaController.TransportControls::class.java) // LiveData to run synchronously Loading @@ -78,6 +85,42 @@ public class SeekBarViewModelTest : SysuiTestCase() { ArchTaskExecutor.getInstance().setDelegate(null) } @Test fun updateRegistersCallback() { viewModel.updateController(mockController) verify(mockController).registerCallback(any()) } @Test fun updateSecondTimeDoesNotRepeatRegistration() { viewModel.updateController(mockController) viewModel.updateController(mockController) verify(mockController, times(1)).registerCallback(any()) } @Test fun updateDifferentControllerUnregistersCallback() { viewModel.updateController(mockController) viewModel.updateController(mock(MediaController::class.java)) verify(mockController).unregisterCallback(any()) } @Test fun updateDifferentControllerRegistersCallback() { viewModel.updateController(mockController) val controller2 = mock(MediaController::class.java) whenever(controller2.sessionToken).thenReturn(token2) viewModel.updateController(controller2) verify(controller2).registerCallback(any()) } @Test fun updateToNullUnregistersCallback() { viewModel.updateController(mockController) viewModel.updateController(null) verify(mockController).unregisterCallback(any()) } @Test fun updateDurationWithPlayback() { // GIVEN that the duration is contained within the metadata Loading Loading @@ -374,6 +417,26 @@ public class SeekBarViewModelTest : SysuiTestCase() { assertThat(fakeExecutor.numPending()).isEqualTo(1) } @Test fun playbackChangeQueuesPollTask() { viewModel.updateController(mockController) val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) verify(mockController).registerCallback(captor.capture()) val callback = captor.value // WHEN the callback receives an new state val state = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, 100L, 1f) build() } callback.onPlaybackStateChanged(state) with(fakeExecutor) { advanceClockToNext() runAllReady() } // THEN an update task is queued assertThat(fakeExecutor.numPending()).isEqualTo(1) } @Test fun clearSeekBar() { // GIVEN that the duration is contained within the metadata Loading @@ -399,4 +462,20 @@ public class SeekBarViewModelTest : SysuiTestCase() { // THEN the seek bar is disabled assertThat(viewModel.progress.value!!.enabled).isFalse() } @Test fun clearSeekBarUnregistersCallback() { viewModel.updateController(mockController) viewModel.clearController() fakeExecutor.runAllReady() verify(mockController).unregisterCallback(any()) } @Test fun destroyUnregistersCallback() { viewModel.updateController(mockController) viewModel.onDestroy() fakeExecutor.runAllReady() verify(mockController).unregisterCallback(any()) } } Loading
packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +1 −0 Original line number Diff line number Diff line Loading @@ -147,6 +147,7 @@ public class MediaControlPanel { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } mSeekBarViewModel.onDestroy(); } private void loadDimens() { Loading
packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +30 −1 Original line number Diff line number Diff line Loading @@ -78,7 +78,22 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { val progress: LiveData<Progress> get() = _progress private var controller: MediaController? = null set(value) { if (field?.sessionToken != value?.sessionToken) { field?.unregisterCallback(callback) value?.registerCallback(callback) field = value } } private var playbackState: PlaybackState? = null private var callback = object : MediaController.Callback() { override fun onPlaybackStateChanged(state: PlaybackState) { playbackState = state if (shouldPollPlaybackPosition()) { checkPlaybackPosition() } } } /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true Loading @@ -95,6 +110,9 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { @WorkerThread fun onSeek(position: Long) { controller?.transportControls?.seekTo(position) // Invalidate the cached playbackState to avoid the thumb jumping back to the previous // position. playbackState = null } /** Loading Loading @@ -125,12 +143,23 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { */ @AnyThread fun clearController() = bgExecutor.execute { controller = null playbackState = null _data = _data.copy(enabled = false) } /** * Call to clean up any resources. */ @AnyThread fun onDestroy() { controller = null playbackState = null } @AnyThread private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ val duration = _data?.duration ?: -1 val duration = _data.duration ?: -1 val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt() if (currentPosition != null && _data.elapsedTime != currentPosition) { _data = _data.copy(elapsedTime = currentPosition) Loading
packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +79 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.media import android.media.MediaMetadata import android.media.session.MediaController import android.media.session.MediaSession import android.media.session.PlaybackState import android.testing.AndroidTestingRunner import android.testing.TestableLooper Loading @@ -35,9 +36,12 @@ import org.junit.After 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.any import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever Loading @@ -61,12 +65,15 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @Mock private lateinit var mockController: MediaController @Mock private lateinit var mockTransport: MediaController.TransportControls private val token1 = MediaSession.Token(1, null) private val token2 = MediaSession.Token(2, null) @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) viewModel = SeekBarViewModel(fakeExecutor) mockController = mock(MediaController::class.java) whenever(mockController.sessionToken).thenReturn(token1) mockTransport = mock(MediaController.TransportControls::class.java) // LiveData to run synchronously Loading @@ -78,6 +85,42 @@ public class SeekBarViewModelTest : SysuiTestCase() { ArchTaskExecutor.getInstance().setDelegate(null) } @Test fun updateRegistersCallback() { viewModel.updateController(mockController) verify(mockController).registerCallback(any()) } @Test fun updateSecondTimeDoesNotRepeatRegistration() { viewModel.updateController(mockController) viewModel.updateController(mockController) verify(mockController, times(1)).registerCallback(any()) } @Test fun updateDifferentControllerUnregistersCallback() { viewModel.updateController(mockController) viewModel.updateController(mock(MediaController::class.java)) verify(mockController).unregisterCallback(any()) } @Test fun updateDifferentControllerRegistersCallback() { viewModel.updateController(mockController) val controller2 = mock(MediaController::class.java) whenever(controller2.sessionToken).thenReturn(token2) viewModel.updateController(controller2) verify(controller2).registerCallback(any()) } @Test fun updateToNullUnregistersCallback() { viewModel.updateController(mockController) viewModel.updateController(null) verify(mockController).unregisterCallback(any()) } @Test fun updateDurationWithPlayback() { // GIVEN that the duration is contained within the metadata Loading Loading @@ -374,6 +417,26 @@ public class SeekBarViewModelTest : SysuiTestCase() { assertThat(fakeExecutor.numPending()).isEqualTo(1) } @Test fun playbackChangeQueuesPollTask() { viewModel.updateController(mockController) val captor = ArgumentCaptor.forClass(MediaController.Callback::class.java) verify(mockController).registerCallback(captor.capture()) val callback = captor.value // WHEN the callback receives an new state val state = PlaybackState.Builder().run { setState(PlaybackState.STATE_PLAYING, 100L, 1f) build() } callback.onPlaybackStateChanged(state) with(fakeExecutor) { advanceClockToNext() runAllReady() } // THEN an update task is queued assertThat(fakeExecutor.numPending()).isEqualTo(1) } @Test fun clearSeekBar() { // GIVEN that the duration is contained within the metadata Loading @@ -399,4 +462,20 @@ public class SeekBarViewModelTest : SysuiTestCase() { // THEN the seek bar is disabled assertThat(viewModel.progress.value!!.enabled).isFalse() } @Test fun clearSeekBarUnregistersCallback() { viewModel.updateController(mockController) viewModel.clearController() fakeExecutor.runAllReady() verify(mockController).unregisterCallback(any()) } @Test fun destroyUnregistersCallback() { viewModel.updateController(mockController) viewModel.onDestroy() fakeExecutor.runAllReady() verify(mockController).unregisterCallback(any()) } }