Merge "Listen for playback state changes" into rvc-dev am: 615aa632b5

Change-Id: If711e44daac81d6faeb048c5b87ae4c938f828be
This commit is contained in:
TreeHugger Robot
2020-05-20 18:04:05 +00:00
committed by Automerger Merge Worker
3 changed files with 110 additions and 1 deletions

View File

@@ -147,6 +147,7 @@ public class MediaControlPanel {
if (mSeekBarObserver != null) {
mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
}
mSeekBarViewModel.onDestroy();
}
private void loadDimens() {

View File

@@ -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)

View File

@@ -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
@@ -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
@@ -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
@@ -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())
}
}