diff options
| author | 2020-05-20 17:54:27 +0000 | |
|---|---|---|
| committer | 2020-05-20 17:54:27 +0000 | |
| commit | 615aa632b5bad7d98f765a73f6f75e87978fc59a (patch) | |
| tree | 9f96717ec324628742d24cfa62f49a51b5afc9d5 | |
| parent | 3c3bf520ff4a86190722b0b6425b8323eae8cd6d (diff) | |
| parent | 734463e13b122cacd67907439707609e9a2d8cc6 (diff) | |
Merge "Listen for playback state changes" into rvc-dev
3 files changed, 110 insertions, 1 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index f90798bd30b8..90054d61c673 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -147,6 +147,7 @@ public class MediaControlPanel { if (mSeekBarObserver != null) { mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver); } + mSeekBarViewModel.onDestroy(); } private void loadDimens() { diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index b08124b54953..06821cd615a5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -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 @@ -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 } /** @@ -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) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt index 1bbf24f27a75..19e15b3c4307 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -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 @@ -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 @@ -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 @@ -79,6 +86,42 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @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 val duration = 12000L @@ -375,6 +418,26 @@ public class SeekBarViewModelTest : SysuiTestCase() { } @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 val metadata = MediaMetadata.Builder().run { @@ -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()) + } } |