Merge "Listen for playback state changes" into rvc-dev am: 615aa632b5
Change-Id: If711e44daac81d6faeb048c5b87ae4c938f828be
This commit is contained in:
@@ -147,6 +147,7 @@ public class MediaControlPanel {
|
||||
if (mSeekBarObserver != null) {
|
||||
mSeekBarViewModel.getProgress().removeObserver(mSeekBarObserver);
|
||||
}
|
||||
mSeekBarViewModel.onDestroy();
|
||||
}
|
||||
|
||||
private void loadDimens() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user