Merge "Move media expiration to MediaDataManager" into rvc-dev
This commit is contained in:
@@ -35,7 +35,8 @@ data class MediaData(
|
|||||||
val packageName: String?,
|
val packageName: String?,
|
||||||
val token: MediaSession.Token?,
|
val token: MediaSession.Token?,
|
||||||
val clickIntent: PendingIntent?,
|
val clickIntent: PendingIntent?,
|
||||||
val device: MediaDeviceData?
|
val device: MediaDeviceData?,
|
||||||
|
val notificationKey: String = "INVALID"
|
||||||
)
|
)
|
||||||
|
|
||||||
/** State of a media action. */
|
/** State of a media action. */
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ import com.android.internal.graphics.ColorUtils
|
|||||||
import com.android.systemui.dagger.qualifiers.Background
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
|
import com.android.systemui.statusbar.notification.MediaNotificationProcessor
|
||||||
|
import com.android.systemui.statusbar.notification.NotificationEntryManager
|
||||||
|
import com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON
|
||||||
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
import com.android.systemui.statusbar.notification.row.HybridGroupManager
|
||||||
import com.android.systemui.util.Assert
|
import com.android.systemui.util.Assert
|
||||||
import com.android.systemui.util.Utils
|
import com.android.systemui.util.Utils
|
||||||
@@ -77,6 +79,8 @@ fun isMediaNotification(sbn: StatusBarNotification): Boolean {
|
|||||||
class MediaDataManager @Inject constructor(
|
class MediaDataManager @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val mediaControllerFactory: MediaControllerFactory,
|
private val mediaControllerFactory: MediaControllerFactory,
|
||||||
|
private val mediaTimeoutListener: MediaTimeoutListener,
|
||||||
|
private val notificationEntryManager: NotificationEntryManager,
|
||||||
@Background private val backgroundExecutor: Executor,
|
@Background private val backgroundExecutor: Executor,
|
||||||
@Main private val foregroundExecutor: Executor
|
@Main private val foregroundExecutor: Executor
|
||||||
) {
|
) {
|
||||||
@@ -84,6 +88,12 @@ class MediaDataManager @Inject constructor(
|
|||||||
private val listeners: MutableSet<Listener> = mutableSetOf()
|
private val listeners: MutableSet<Listener> = mutableSetOf()
|
||||||
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
|
private val mediaEntries: LinkedHashMap<String, MediaData> = LinkedHashMap()
|
||||||
|
|
||||||
|
init {
|
||||||
|
mediaTimeoutListener.timeoutCallback = { token: String, timedOut: Boolean ->
|
||||||
|
setTimedOut(token, timedOut) }
|
||||||
|
addListener(mediaTimeoutListener)
|
||||||
|
}
|
||||||
|
|
||||||
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
fun onNotificationAdded(key: String, sbn: StatusBarNotification) {
|
||||||
if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
|
if (Utils.useQsMediaPlayer(context) && isMediaNotification(sbn)) {
|
||||||
Assert.isMainThread()
|
Assert.isMainThread()
|
||||||
@@ -112,6 +122,16 @@ class MediaDataManager @Inject constructor(
|
|||||||
*/
|
*/
|
||||||
fun removeListener(listener: Listener) = listeners.remove(listener)
|
fun removeListener(listener: Listener) = listeners.remove(listener)
|
||||||
|
|
||||||
|
private fun setTimedOut(token: String, timedOut: Boolean) {
|
||||||
|
if (!timedOut) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mediaEntries[token]?.let {
|
||||||
|
notificationEntryManager.removeNotification(it.notificationKey, null /* ranking */,
|
||||||
|
UNDEFINED_DISMISS_REASON)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
|
private fun loadMediaDataInBg(key: String, sbn: StatusBarNotification) {
|
||||||
val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
|
val token = sbn.notification.extras.getParcelable(Notification.EXTRA_MEDIA_SESSION)
|
||||||
as MediaSession.Token?
|
as MediaSession.Token?
|
||||||
@@ -223,7 +243,7 @@ class MediaDataManager @Inject constructor(
|
|||||||
foregroundExecutor.execute {
|
foregroundExecutor.execute {
|
||||||
onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
|
onMediaDataLoaded(key, MediaData(true, bgColor, app, smallIconDrawable, artist, song,
|
||||||
artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
|
artWorkIcon, actionIcons, actionsToShowCollapsed, sbn.packageName, token,
|
||||||
notif.contentIntent, null))
|
notif.contentIntent, null, key))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.media
|
||||||
|
|
||||||
|
import android.media.session.MediaController
|
||||||
|
import android.media.session.PlaybackState
|
||||||
|
import android.os.SystemProperties
|
||||||
|
import android.util.Log
|
||||||
|
import com.android.systemui.dagger.qualifiers.Main
|
||||||
|
import com.android.systemui.statusbar.NotificationMediaManager.isPlayingState
|
||||||
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
private const val DEBUG = true
|
||||||
|
private const val TAG = "MediaTimeout"
|
||||||
|
private val PAUSED_MEDIA_TIMEOUT = SystemProperties
|
||||||
|
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller responsible for keeping track of playback states and expiring inactive streams.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class MediaTimeoutListener @Inject constructor(
|
||||||
|
private val mediaControllerFactory: MediaControllerFactory,
|
||||||
|
@Main private val mainExecutor: DelayableExecutor
|
||||||
|
) : MediaDataManager.Listener {
|
||||||
|
|
||||||
|
private val mediaListeners: MutableMap<String, PlaybackStateListener> = mutableMapOf()
|
||||||
|
|
||||||
|
lateinit var timeoutCallback: (String, Boolean) -> Unit
|
||||||
|
|
||||||
|
override fun onMediaDataLoaded(key: String, data: MediaData) {
|
||||||
|
if (mediaListeners.containsKey(key)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mediaListeners[key] = PlaybackStateListener(key, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMediaDataRemoved(key: String) {
|
||||||
|
mediaListeners.remove(key)?.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isTimedOut(key: String): Boolean {
|
||||||
|
return mediaListeners[key]?.timedOut ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class PlaybackStateListener(
|
||||||
|
private val key: String,
|
||||||
|
data: MediaData
|
||||||
|
) : MediaController.Callback() {
|
||||||
|
|
||||||
|
var timedOut = false
|
||||||
|
|
||||||
|
private val mediaController = mediaControllerFactory.create(data.token)
|
||||||
|
private var cancellation: Runnable? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
mediaController.registerCallback(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destroy() {
|
||||||
|
mediaController.unregisterCallback(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPlaybackStateChanged(state: PlaybackState?) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.v(TAG, "onPlaybackStateChanged: $state")
|
||||||
|
}
|
||||||
|
expireMediaTimeout(key, "playback state ativity - $state, $key")
|
||||||
|
|
||||||
|
if (state == null || !isPlayingState(state.state)) {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.v(TAG, "schedule timeout for $key")
|
||||||
|
}
|
||||||
|
expireMediaTimeout(key, "PLAYBACK STATE CHANGED - $state")
|
||||||
|
cancellation = mainExecutor.executeDelayed({
|
||||||
|
cancellation = null
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.v(TAG, "Execute timeout for $key")
|
||||||
|
}
|
||||||
|
timedOut = true
|
||||||
|
timeoutCallback(key, timedOut)
|
||||||
|
}, PAUSED_MEDIA_TIMEOUT)
|
||||||
|
} else {
|
||||||
|
timedOut = false
|
||||||
|
timeoutCallback(key, timedOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun expireMediaTimeout(mediaNotificationKey: String, reason: String) {
|
||||||
|
cancellation?.apply {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.v(TAG,
|
||||||
|
"media timeout cancelled for $mediaNotificationKey, reason: $reason")
|
||||||
|
}
|
||||||
|
run()
|
||||||
|
}
|
||||||
|
cancellation = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
package com.android.systemui.statusbar;
|
package com.android.systemui.statusbar;
|
||||||
|
|
||||||
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
|
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
|
||||||
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
|
|
||||||
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
|
import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_MEDIA_FAKE_ARTWORK;
|
||||||
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
|
import static com.android.systemui.statusbar.phone.StatusBar.ENABLE_LOCKSCREEN_WALLPAPER;
|
||||||
import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
|
import static com.android.systemui.statusbar.phone.StatusBar.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
|
||||||
@@ -36,7 +35,6 @@ import android.media.session.MediaSession;
|
|||||||
import android.media.session.MediaSessionManager;
|
import android.media.session.MediaSessionManager;
|
||||||
import android.media.session.PlaybackState;
|
import android.media.session.PlaybackState;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.SystemProperties;
|
|
||||||
import android.os.Trace;
|
import android.os.Trace;
|
||||||
import android.os.UserHandle;
|
import android.os.UserHandle;
|
||||||
import android.provider.DeviceConfig;
|
import android.provider.DeviceConfig;
|
||||||
@@ -80,7 +78,6 @@ import java.util.Collection;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
|
||||||
@@ -91,8 +88,6 @@ import dagger.Lazy;
|
|||||||
public class NotificationMediaManager implements Dumpable {
|
public class NotificationMediaManager implements Dumpable {
|
||||||
private static final String TAG = "NotificationMediaManager";
|
private static final String TAG = "NotificationMediaManager";
|
||||||
public static final boolean DEBUG_MEDIA = false;
|
public static final boolean DEBUG_MEDIA = false;
|
||||||
private static final long PAUSED_MEDIA_TIMEOUT = SystemProperties
|
|
||||||
.getLong("debug.sysui.media_timeout", TimeUnit.MINUTES.toMillis(10));
|
|
||||||
|
|
||||||
private final StatusBarStateController mStatusBarStateController
|
private final StatusBarStateController mStatusBarStateController
|
||||||
= Dependency.get(StatusBarStateController.class);
|
= Dependency.get(StatusBarStateController.class);
|
||||||
@@ -134,7 +129,6 @@ public class NotificationMediaManager implements Dumpable {
|
|||||||
private MediaController mMediaController;
|
private MediaController mMediaController;
|
||||||
private String mMediaNotificationKey;
|
private String mMediaNotificationKey;
|
||||||
private MediaMetadata mMediaMetadata;
|
private MediaMetadata mMediaMetadata;
|
||||||
private Runnable mMediaTimeoutCancellation;
|
|
||||||
|
|
||||||
private BackDropView mBackdrop;
|
private BackDropView mBackdrop;
|
||||||
private ImageView mBackdropFront;
|
private ImageView mBackdropFront;
|
||||||
@@ -164,47 +158,11 @@ public class NotificationMediaManager implements Dumpable {
|
|||||||
if (DEBUG_MEDIA) {
|
if (DEBUG_MEDIA) {
|
||||||
Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
|
Log.v(TAG, "DEBUG_MEDIA: onPlaybackStateChanged: " + state);
|
||||||
}
|
}
|
||||||
if (mMediaTimeoutCancellation != null) {
|
|
||||||
if (DEBUG_MEDIA) {
|
|
||||||
Log.v(TAG, "DEBUG_MEDIA: media timeout cancelled");
|
|
||||||
}
|
|
||||||
mMediaTimeoutCancellation.run();
|
|
||||||
mMediaTimeoutCancellation = null;
|
|
||||||
}
|
|
||||||
if (state != null) {
|
if (state != null) {
|
||||||
if (!isPlaybackActive(state.getState())) {
|
if (!isPlaybackActive(state.getState())) {
|
||||||
clearCurrentMediaNotification();
|
clearCurrentMediaNotification();
|
||||||
}
|
}
|
||||||
findAndUpdateMediaNotifications();
|
findAndUpdateMediaNotifications();
|
||||||
scheduleMediaTimeout(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleMediaTimeout(PlaybackState state) {
|
|
||||||
final NotificationEntry entry;
|
|
||||||
synchronized (mEntryManager) {
|
|
||||||
entry = mEntryManager.getActiveNotificationUnfiltered(mMediaNotificationKey);
|
|
||||||
}
|
|
||||||
if (entry != null) {
|
|
||||||
if (!isPlayingState(state.getState())) {
|
|
||||||
if (DEBUG_MEDIA) {
|
|
||||||
Log.v(TAG, "DEBUG_MEDIA: schedule timeout for "
|
|
||||||
+ mMediaNotificationKey);
|
|
||||||
}
|
|
||||||
mMediaTimeoutCancellation = mMainExecutor.executeDelayed(() -> {
|
|
||||||
synchronized (mEntryManager) {
|
|
||||||
if (DEBUG_MEDIA) {
|
|
||||||
Log.v(TAG, "DEBUG_MEDIA: execute timeout for "
|
|
||||||
+ mMediaNotificationKey);
|
|
||||||
}
|
|
||||||
if (mMediaNotificationKey == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mEntryManager.removeNotification(mMediaNotificationKey, null,
|
|
||||||
UNDEFINED_DISMISS_REASON);
|
|
||||||
}
|
|
||||||
}, PAUSED_MEDIA_TIMEOUT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
|
|||||||
mManager.addListener(mListener);
|
mManager.addListener(mListener);
|
||||||
|
|
||||||
mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
mMediaData = new MediaData(true, BG_COLOR, APP, null, ARTIST, TITLE, null,
|
||||||
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null);
|
new ArrayList<>(), new ArrayList<>(), PACKAGE, null, null, null, KEY);
|
||||||
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
|
mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.android.systemui.media
|
||||||
|
|
||||||
|
import android.media.session.MediaController
|
||||||
|
import android.media.session.PlaybackState
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.SysuiTestCase
|
||||||
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
|
import com.android.systemui.util.mockito.capture
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.ArgumentCaptor
|
||||||
|
import org.mockito.ArgumentMatchers.any
|
||||||
|
import org.mockito.ArgumentMatchers.anyLong
|
||||||
|
import org.mockito.Captor
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.Mockito.clearInvocations
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.junit.MockitoJUnit
|
||||||
|
|
||||||
|
private const val KEY = "KEY"
|
||||||
|
|
||||||
|
private fun <T> eq(value: T): T = Mockito.eq(value) ?: value
|
||||||
|
private fun <T> anyObject(): T {
|
||||||
|
return Mockito.anyObject<T>()
|
||||||
|
}
|
||||||
|
|
||||||
|
@SmallTest
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
class MediaTimeoutListenerTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
@Mock private lateinit var mediaControllerFactory: MediaControllerFactory
|
||||||
|
@Mock private lateinit var mediaController: MediaController
|
||||||
|
@Mock private lateinit var executor: DelayableExecutor
|
||||||
|
@Mock private lateinit var mediaData: MediaData
|
||||||
|
@Mock private lateinit var timeoutCallback: (String, Boolean) -> Unit
|
||||||
|
@Mock private lateinit var cancellationRunnable: Runnable
|
||||||
|
@Captor private lateinit var timeoutCaptor: ArgumentCaptor<Runnable>
|
||||||
|
@Captor private lateinit var mediaCallbackCaptor: ArgumentCaptor<MediaController.Callback>
|
||||||
|
@JvmField @Rule val mockito = MockitoJUnit.rule()
|
||||||
|
private lateinit var mediaTimeoutListener: MediaTimeoutListener
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
`when`(mediaControllerFactory.create(any())).thenReturn(mediaController)
|
||||||
|
`when`(executor.executeDelayed(any(), anyLong())).thenReturn(cancellationRunnable)
|
||||||
|
mediaTimeoutListener = MediaTimeoutListener(mediaControllerFactory, executor)
|
||||||
|
mediaTimeoutListener.timeoutCallback = timeoutCallback
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnMediaDataLoaded_registersPlaybackListener() {
|
||||||
|
mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
|
||||||
|
verify(mediaController).registerCallback(capture(mediaCallbackCaptor))
|
||||||
|
|
||||||
|
// Ignores is same key
|
||||||
|
clearInvocations(mediaController)
|
||||||
|
mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
|
||||||
|
verify(mediaController, never()).registerCallback(anyObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnMediaDataRemoved_unregistersPlaybackListener() {
|
||||||
|
mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
|
||||||
|
mediaTimeoutListener.onMediaDataRemoved(KEY)
|
||||||
|
verify(mediaController).unregisterCallback(anyObject())
|
||||||
|
|
||||||
|
// Ignores duplicate requests
|
||||||
|
clearInvocations(mediaController)
|
||||||
|
mediaTimeoutListener.onMediaDataRemoved(KEY)
|
||||||
|
verify(mediaController, never()).unregisterCallback(anyObject())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() {
|
||||||
|
// Assuming we're registered
|
||||||
|
testOnMediaDataLoaded_registersPlaybackListener()
|
||||||
|
|
||||||
|
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
||||||
|
.setState(PlaybackState.STATE_PAUSED, 0L, 0f).build())
|
||||||
|
verify(executor).executeDelayed(capture(timeoutCaptor), anyLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testOnPlaybackStateChanged_cancelsTimeout_whenResumed() {
|
||||||
|
// Assuming we're have a pending timeout
|
||||||
|
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
|
||||||
|
|
||||||
|
mediaCallbackCaptor.value.onPlaybackStateChanged(PlaybackState.Builder()
|
||||||
|
.setState(PlaybackState.STATE_PLAYING, 0L, 0f).build())
|
||||||
|
verify(cancellationRunnable).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTimeoutCallback_invokedIfTimeout() {
|
||||||
|
// Assuming we're have a pending timeout
|
||||||
|
testOnPlaybackStateChanged_schedulesTimeout_whenPaused()
|
||||||
|
|
||||||
|
timeoutCaptor.value.run()
|
||||||
|
verify(timeoutCallback).invoke(eq(KEY), eq(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testIsTimedOut() {
|
||||||
|
mediaTimeoutListener.onMediaDataLoaded(KEY, mediaData)
|
||||||
|
assertThat(mediaTimeoutListener.isTimedOut(KEY)).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user