Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 50ea27aa authored by Lucas Dupin's avatar Lucas Dupin Committed by Android (Google) Code Review
Browse files

Merge "Update listener instead of creating a new one"

parents c9b17e74 f5332923
Loading
Loading
Loading
Loading
+38 −27
Original line number Diff line number Diff line
@@ -54,25 +54,22 @@ class MediaTimeoutListener @Inject constructor(
        if (mediaListeners.containsKey(key)) {
            return
        }
        // Having an old key means that we're migrating from/to resumption. We should invalidate
        // the old listener and create a new one.
        // Having an old key means that we're migrating from/to resumption. We should update
        // the old listener to make sure that events will be dispatched to the new location.
        val migrating = oldKey != null && key != oldKey
        var wasPlaying = false
        if (migrating) {
            if (mediaListeners.containsKey(oldKey)) {
                val oldListener = mediaListeners.remove(oldKey)
                wasPlaying = oldListener?.playing ?: false
                oldListener?.destroy()
            val reusedListener = mediaListeners.remove(oldKey)
            if (reusedListener != null) {
                wasPlaying = reusedListener.playing ?: false
                if (DEBUG) Log.d(TAG, "migrating key $oldKey to $key, for resumption")
            } else {
                Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
            }
        }
        mediaListeners[key] = PlaybackStateListener(key, data)

        // If a player becomes active because of a migration, we'll need to broadcast its state.
        // Doing it now would lead to reentrant callbacks, so let's wait until we're done.
        if (migrating && mediaListeners[key]?.playing != wasPlaying) {
                reusedListener.mediaData = data
                reusedListener.key = key
                mediaListeners[key] = reusedListener
                if (wasPlaying != reusedListener.playing) {
                    // If a player becomes active because of a migration, we'll need to broadcast
                    // its state. Doing it now would lead to reentrant callbacks, so let's wait
                    // until we're done.
                    mainExecutor.execute {
                        if (mediaListeners[key]?.playing == true) {
                            if (DEBUG) Log.d(TAG, "deliver delayed playback state for $key")
@@ -80,6 +77,12 @@ class MediaTimeoutListener @Inject constructor(
                        }
                    }
                }
                return
            } else {
                Log.w(TAG, "Old key $oldKey for player $key doesn't exist. Continuing...")
            }
        }
        mediaListeners[key] = PlaybackStateListener(key, data)
    }

    override fun onMediaDataRemoved(key: String) {
@@ -91,28 +94,36 @@ class MediaTimeoutListener @Inject constructor(
    }

    private inner class PlaybackStateListener(
        private val key: String,
        var key: String,
        data: MediaData
    ) : MediaController.Callback() {

        var timedOut = false
        var playing: Boolean? = null

        // Resume controls may have null token
        private val mediaController = if (data.token != null) {
            mediaControllerFactory.create(data.token)
        var mediaData: MediaData = data
            set(value) {
                mediaController?.unregisterCallback(this)
                field = value
                mediaController = if (field.token != null) {
                    mediaControllerFactory.create(field.token)
                } else {
                    null
                }
        private var cancellation: Runnable? = null

        init {
                mediaController?.registerCallback(this)
                // Let's register the cancellations, but not dispatch events now.
                // Timeouts didn't happen yet and reentrant events are troublesome.
                processState(mediaController?.playbackState, dispatchEvents = false)
            }

        // Resume controls may have null token
        private var mediaController: MediaController? = null
        private var cancellation: Runnable? = null

        init {
            mediaData = data
        }

        fun destroy() {
            mediaController?.unregisterCallback(this)
        }
+16 −0
Original line number Diff line number Diff line
@@ -154,6 +154,22 @@ class MediaTimeoutListenerTest : SysuiTestCase() {
        verify(executor).execute(anyObject())
    }

    @Test
    fun testOnMediaDataLoaded_migratesKeys_noTimeoutExtension() {
        // From not playing
        mediaTimeoutListener.onMediaDataLoaded(KEY, null, mediaData)
        clearInvocations(mediaController)

        // Migrate, still not playing
        val playingState = mock(android.media.session.PlaybackState::class.java)
        `when`(playingState.state).thenReturn(PlaybackState.STATE_PAUSED)
        `when`(mediaController.playbackState).thenReturn(playingState)
        mediaTimeoutListener.onMediaDataLoaded("NEWKEY", KEY, mediaData)

        // Never cancels callback, or schedule another one
        verify(cancellationRunnable, never()).run()
    }

    @Test
    fun testOnPlaybackStateChanged_schedulesTimeout_whenPaused() {
        // Assuming we're registered