summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author TreeHugger Robot <treehugger-gerrit@google.com> 2020-05-20 17:54:27 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-05-20 17:54:27 +0000
commit615aa632b5bad7d98f765a73f6f75e87978fc59a (patch)
tree9f96717ec324628742d24cfa62f49a51b5afc9d5
parent3c3bf520ff4a86190722b0b6425b8323eae8cd6d (diff)
parent734463e13b122cacd67907439707609e9a2d8cc6 (diff)
Merge "Listen for playback state changes" into rvc-dev
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt79
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())
+ }
}