Commit 8538d5c6 authored by Sebastian Kaspari's avatar Sebastian Kaspari
Browse files

Closes #3795: Do not show media notification for media with very short duration.

parent 12e1547e
Loading
Loading
Loading
Loading
+32 −20
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
package mozilla.components.feature.media

import android.content.Context
import mozilla.components.feature.media.ext.hasMediaWithSufficientLongDuration
import mozilla.components.feature.media.service.MediaService
import mozilla.components.feature.media.state.MediaState
import mozilla.components.feature.media.state.MediaStateMachine
@@ -22,35 +23,46 @@ import mozilla.components.feature.media.state.MediaStateMachine
class MediaFeature(
    private val context: Context
) {
    private var serviceRunning = false
    private var serviceStarted = false
    private var observer = object : MediaStateMachine.Observer {
        override fun onStateChanged(state: MediaState) {
            notifyService(state)
        }
    }

    /**
     * Enables the feature.
     */
    fun enable() {
        MediaStateMachine.register(MediaObserver(this))
        MediaStateMachine.register(observer)

        // MediaStateMachine will only notify us about state changes but not pass the current state
        // to us - which might already be "playing". So let's process the current state now.
        notifyService(MediaStateMachine.state)
    }

    internal fun startMediaService() {
    @Suppress("LongMethod")
    private fun notifyService(state: MediaState) {
        when (state) {
            is MediaState.Playing -> {
                if (state.hasMediaWithSufficientLongDuration()) {
                    MediaService.updateState(context)
        serviceRunning = true
                    serviceStarted = true
                }
            }

    internal fun stopMediaService() {
        if (serviceRunning) {
            is MediaState.Paused -> {
                if (serviceStarted) {
                    MediaService.updateState(context)
                }
            }
}

internal class MediaObserver(
    private val feature: MediaFeature
) : MediaStateMachine.Observer {
    override fun onStateChanged(state: MediaState) {
        if (state is MediaState.Playing || state is MediaState.Paused) {
            feature.startMediaService()
        } else if (state is MediaState.None) {
            feature.stopMediaService()
            is MediaState.None -> {
                if (serviceStarted) {
                    MediaService.updateState(context)
                    serviceStarted = false
                }
            }
        }
    }
}
+19 −0
Original line number Diff line number Diff line
@@ -8,6 +8,11 @@ import android.support.v4.media.session.PlaybackStateCompat
import mozilla.components.concept.engine.media.Media
import mozilla.components.feature.media.state.MediaState

/**
 * The minimum duration (in seconds) for media so that we bother with showing a media notification.
 */
private const val MINIMUM_DURATION_SECONDS = 5

/**
 * Gets the list of [Media] associated with this [MediaState].
 */
@@ -62,3 +67,17 @@ internal fun MediaState.playIfPaused() {
        media.play()
    }
}

internal fun MediaState.hasMediaWithSufficientLongDuration(): Boolean {
    getMedia().forEach { media ->
        if (media.metadata.duration < 0) {
            return true
        }

        if (media.metadata.duration > MINIMUM_DURATION_SECONDS) {
            return true
        }
    }

    return false
}
+35 −1
Original line number Diff line number Diff line
@@ -10,13 +10,13 @@ import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.media.Media
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.media.state.MockMedia
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
@@ -45,6 +45,7 @@ class MediaFeatureTest {

        // A media object gets added to the session
        val media = MockMedia(Media.PlaybackState.UNKNOWN)
        doReturn(30.0).`when`(media.metadata).duration
        session.media = listOf(media)

        media.playbackState = Media.PlaybackState.WAITING
@@ -58,10 +59,42 @@ class MediaFeatureTest {
        verify(context).startService(any())
    }

    @Test
    fun `Media with short duration will not start service`() {
        val context: Context = mock()
        val sessionManager = SessionManager(engine = mock())

        MediaStateMachine.start(sessionManager)

        val feature = MediaFeature(context)
        feature.enable()

        // A session gets added
        val session = Session("https://www.mozilla.org")
        sessionManager.add(session)

        // A media object gets added to the session
        val media = MockMedia(Media.PlaybackState.UNKNOWN)
        doReturn(2.0).`when`(media.metadata).duration
        session.media = listOf(media)

        media.playbackState = Media.PlaybackState.WAITING

        // So far nothing has happened yet
        verify(context, never()).startService(any())

        // Media starts playing!
        media.playbackState = Media.PlaybackState.PLAYING

        // Service still not started since duration is too short
        verify(context, never()).startService(any())
    }

    @Test
    fun `Media switching from playing to pause send Intent to service`() {
        val context: Context = mock()
        val media = MockMedia(Media.PlaybackState.PLAYING)
        doReturn(30.0).`when`(media.metadata).duration

        val sessionManager = SessionManager(engine = mock()).apply {
            add(Session("https://www.mozilla.org").also { it.media = listOf(media) })
@@ -84,6 +117,7 @@ class MediaFeatureTest {
    fun `Media stopping to play will notify service`() {
        val context: Context = mock()
        val media = MockMedia(Media.PlaybackState.UNKNOWN)
        doReturn(30.0).`when`(media.metadata).duration

        val sessionManager = SessionManager(engine = mock()).apply {
            add(Session("https://www.mozilla.org").also { it.media = listOf(media) })
+17 −0
Original line number Diff line number Diff line
package mozilla.components.feature.media

import mozilla.components.concept.engine.media.Media
import mozilla.components.support.test.mock

internal class MockMedia(
    initialState: PlaybackState
) : Media() {
    init {
        playbackState = initialState
    }

    override val controller: Controller =
        mock()
    override val metadata: Metadata =
        mock()
}
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -9,9 +9,9 @@ import android.media.AudioManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.browser.session.Session
import mozilla.components.concept.engine.media.Media
import mozilla.components.feature.media.MockMedia
import mozilla.components.feature.media.state.MediaState
import mozilla.components.feature.media.state.MediaStateMachine
import mozilla.components.feature.media.state.MockMedia
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
Loading