diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6c4d1e1cf4..efb94c3c04 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -232,6 +232,9 @@
+
+
()
private var fullScreenMediaFeature =
ViewBoundFeatureWrapper()
+ private var fullScreenMediaSessionFeature =
+ ViewBoundFeatureWrapper()
private val searchFeature = ViewBoundFeatureWrapper()
private var pipFeature: PictureInPictureFeature? = null
@@ -392,14 +396,25 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
view = view
)
- fullScreenMediaFeature.set(
- feature = MediaFullscreenOrientationFeature(
- requireActivity(),
- context.components.core.store
- ),
- owner = this,
- view = view
- )
+ if (newMediaSessionApi) {
+ fullScreenMediaSessionFeature.set(
+ feature = MediaSessionFullscreenFeature(
+ requireActivity(),
+ context.components.core.store
+ ),
+ owner = this,
+ view = view
+ )
+ } else {
+ fullScreenMediaFeature.set(
+ feature = MediaFullscreenOrientationFeature(
+ requireActivity(),
+ context.components.core.store
+ ),
+ owner = this,
+ view = view
+ )
+ }
val downloadFeature = DownloadsFeature(
context.applicationContext,
diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt
index 405bf5c8da..1161ae7fa3 100644
--- a/app/src/main/java/org/mozilla/fenix/components/Core.kt
+++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt
@@ -39,7 +39,6 @@ import mozilla.components.concept.fetch.Client
import mozilla.components.feature.customtabs.store.CustomTabsServiceStore
import mozilla.components.feature.downloads.DownloadMiddleware
import mozilla.components.feature.logins.exceptions.LoginExceptionStorage
-import mozilla.components.feature.media.middleware.MediaMiddleware
import mozilla.components.feature.media.middleware.RecordingDevicesMiddleware
import mozilla.components.feature.pwa.ManifestStorage
import mozilla.components.feature.pwa.WebAppShortcutManager
@@ -74,7 +73,6 @@ import org.mozilla.fenix.downloads.DownloadService
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.lazyMonitored
-import org.mozilla.fenix.media.MediaService
import org.mozilla.fenix.search.telemetry.ads.AdsTelemetry
import org.mozilla.fenix.search.telemetry.incontent.InContentTelemetry
import org.mozilla.fenix.settings.SupportUtils
@@ -82,6 +80,11 @@ import org.mozilla.fenix.settings.advanced.getSelectedLocale
import org.mozilla.fenix.utils.Mockable
import org.mozilla.fenix.utils.getUndoDelay
import java.util.concurrent.TimeUnit
+import mozilla.components.feature.media.MediaSessionFeature
+import mozilla.components.feature.media.middleware.MediaMiddleware
+import org.mozilla.fenix.FeatureFlags.newMediaSessionApi
+import org.mozilla.fenix.media.MediaService
+import org.mozilla.fenix.media.MediaSessionService
/**
* Component group for all core browser functionality.
@@ -179,10 +182,9 @@ class Core(
* The [BrowserStore] holds the global [BrowserState].
*/
val store by lazyMonitored {
- BrowserStore(
- middleware = listOf(
+ val middlewareList =
+ mutableListOf(
RecentlyClosedMiddleware(context, RECENTLY_CLOSED_MAX, engine),
- MediaMiddleware(context, MediaService::class.java),
DownloadMiddleware(context, DownloadService::class.java),
ReaderViewMiddleware(),
TelemetryMiddleware(
@@ -199,7 +201,14 @@ class Core(
migration = SearchMigration(context)
),
RecordingDevicesMiddleware(context)
- ) + EngineMiddleware.create(engine, ::findSessionById)
+ )
+
+ if (!newMediaSessionApi) {
+ middlewareList.add(MediaMiddleware(context, MediaService::class.java))
+ }
+
+ BrowserStore(
+ middleware = middlewareList + EngineMiddleware.create(engine, ::findSessionById)
)
}
@@ -278,6 +287,10 @@ class Core(
context, engine, icons, R.drawable.ic_status_logo,
permissionStorage.permissionsStorage, HomeActivity::class.java
)
+
+ if (newMediaSessionApi) {
+ MediaSessionFeature(context, MediaSessionService::class.java, store).start()
+ }
}
}
diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessor.kt
index 6182742727..4e91e5ef40 100644
--- a/app/src/main/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessor.kt
+++ b/app/src/main/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessor.kt
@@ -7,24 +7,26 @@ package org.mozilla.fenix.home.intent
import android.content.Intent
import androidx.navigation.NavController
import mozilla.components.feature.media.service.AbstractMediaService
+import mozilla.components.feature.media.service.AbstractMediaSessionService
import org.mozilla.fenix.BrowserDirection
+import org.mozilla.fenix.FeatureFlags.newMediaSessionApi
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.ext.components
/**
* When the media notification is clicked we need to switch to the tab where the audio/video is
* playing. This intent has the following informations:
- * action - [AbstractMediaService.Companion.ACTION_SWITCH_TAB]
- * extra string for the tab id - [AbstractMediaService.Companion.EXTRA_TAB_ID]
+ * action - [AbstractMediaSessionService.Companion.ACTION_SWITCH_TAB]
+ * extra string for the tab id - [AbstractMediaSessionService.Companion.EXTRA_TAB_ID]
*/
class OpenSpecificTabIntentProcessor(
private val activity: HomeActivity
) : HomeIntentProcessor {
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
- if (intent.action == AbstractMediaService.Companion.ACTION_SWITCH_TAB) {
+ if (intent.action == getAction()) {
val sessionManager = activity.components.core.sessionManager
- val sessionId = intent.extras?.getString(AbstractMediaService.Companion.EXTRA_TAB_ID)
+ val sessionId = intent.extras?.getString(getTabId())
val session = sessionId?.let { sessionManager.findSessionById(it) }
if (session != null) {
sessionManager.select(session)
@@ -36,3 +38,19 @@ class OpenSpecificTabIntentProcessor(
return false
}
}
+
+private fun getAction(): String {
+ return if (newMediaSessionApi) {
+ AbstractMediaSessionService.Companion.ACTION_SWITCH_TAB
+ } else {
+ AbstractMediaService.Companion.ACTION_SWITCH_TAB
+ }
+}
+
+private fun getTabId(): String {
+ return if (newMediaSessionApi) {
+ AbstractMediaSessionService.Companion.EXTRA_TAB_ID
+ } else {
+ AbstractMediaService.Companion.EXTRA_TAB_ID
+ }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/media/MediaSessionService.kt b/app/src/main/java/org/mozilla/fenix/media/MediaSessionService.kt
new file mode 100644
index 0000000000..0c08393e10
--- /dev/null
+++ b/app/src/main/java/org/mozilla/fenix/media/MediaSessionService.kt
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.fenix.media
+
+import mozilla.components.browser.state.store.BrowserStore
+import mozilla.components.feature.media.service.AbstractMediaSessionService
+import org.mozilla.fenix.ext.components
+
+/**
+ * [AbstractMediaSessionService] implementation for injecting [BrowserStore] singleton.
+ */
+class MediaSessionService : AbstractMediaSessionService() {
+ override val store: BrowserStore by lazy { components.core.store }
+}
diff --git a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayViewHolder.kt b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayViewHolder.kt
index 49bb453e88..1b811b21a9 100644
--- a/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayViewHolder.kt
+++ b/app/src/main/java/org/mozilla/fenix/tabtray/TabTrayViewHolder.kt
@@ -13,7 +13,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.appcompat.widget.AppCompatImageButton
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.tab_tray_grid_item.view.*
-import mozilla.components.browser.state.state.MediaState
+import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.tabstray.TabViewHolder
import mozilla.components.browser.tabstray.TabsTrayStyling
@@ -21,24 +21,27 @@ import mozilla.components.browser.tabstray.thumbnail.TabThumbnailView
import mozilla.components.browser.toolbar.MAX_URI_LENGTH
import mozilla.components.concept.base.images.ImageLoadRequest
import mozilla.components.concept.base.images.ImageLoader
+import mozilla.components.concept.engine.mediasession.MediaSession
import mozilla.components.concept.tabstray.Tab
import mozilla.components.concept.tabstray.TabsTray
-import mozilla.components.feature.media.ext.pauseIfPlaying
-import mozilla.components.feature.media.ext.playIfPaused
import mozilla.components.support.base.observer.Observable
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.ext.components
-import org.mozilla.fenix.ext.getMediaStateForSession
import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.removeAndDisable
import org.mozilla.fenix.ext.removeTouchDelegate
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showAndEnable
import org.mozilla.fenix.ext.toShortUrl
-import org.mozilla.fenix.utils.Do
import kotlin.math.max
+import mozilla.components.browser.state.state.MediaState
+import mozilla.components.feature.media.ext.pauseIfPlaying
+import mozilla.components.feature.media.ext.playIfPaused
+import org.mozilla.fenix.FeatureFlags.newMediaSessionApi
+import org.mozilla.fenix.ext.getMediaStateForSession
+import org.mozilla.fenix.utils.Do
/**
* A RecyclerView ViewHolder implementation for "tab" items.
@@ -68,6 +71,7 @@ class TabTrayViewHolder(
/**
* Displays the data of the given session and notifies the given observable about events.
*/
+ @Suppress("ComplexMethod", "LongMethod")
override fun bind(
tab: Tab,
isSelected: Boolean,
@@ -94,49 +98,98 @@ class TabTrayViewHolder(
// Media state
playPauseButtonView.increaseTapArea(PLAY_PAUSE_BUTTON_EXTRA_DPS)
- with(playPauseButtonView) {
- invalidate()
- Do exhaustive when (store.state.getMediaStateForSession(tab.id)) {
- MediaState.State.PAUSED -> {
- showAndEnable()
- contentDescription =
- context.getString(R.string.mozac_feature_media_notification_action_play)
- setImageDrawable(
- AppCompatResources.getDrawable(context, R.drawable.media_state_play)
- )
- }
- MediaState.State.PLAYING -> {
- showAndEnable()
- contentDescription =
- context.getString(R.string.mozac_feature_media_notification_action_pause)
- setImageDrawable(
- AppCompatResources.getDrawable(context, R.drawable.media_state_pause)
- )
+ if (newMediaSessionApi) {
+ with(playPauseButtonView) {
+ invalidate()
+ val sessionState = store.state.findTabOrCustomTab(tab.id)
+ when (sessionState?.mediaSessionState?.playbackState) {
+ MediaSession.PlaybackState.PAUSED -> {
+ showAndEnable()
+ contentDescription =
+ context.getString(R.string.mozac_feature_media_notification_action_play)
+ setImageDrawable(
+ AppCompatResources.getDrawable(context, R.drawable.media_state_play)
+ )
+ }
+
+ MediaSession.PlaybackState.PLAYING -> {
+ showAndEnable()
+ contentDescription =
+ context.getString(R.string.mozac_feature_media_notification_action_pause)
+ setImageDrawable(
+ AppCompatResources.getDrawable(context, R.drawable.media_state_pause)
+ )
+ }
+
+ else -> {
+ removeTouchDelegate()
+ removeAndDisable()
+ }
}
- MediaState.State.NONE -> {
- removeTouchDelegate()
- removeAndDisable()
+ setOnClickListener {
+ when (sessionState?.mediaSessionState?.playbackState) {
+ MediaSession.PlaybackState.PLAYING -> {
+ metrics.track(Event.TabMediaPause)
+ sessionState.mediaSessionState?.controller?.pause()
+ }
+
+ MediaSession.PlaybackState.PAUSED -> {
+ metrics.track(Event.TabMediaPlay)
+ sessionState.mediaSessionState?.controller?.play()
+ }
+ else -> throw AssertionError(
+ "Play/Pause button clicked without play/pause state."
+ )
+ }
}
}
- }
+ } else {
+ with(playPauseButtonView) {
+ invalidate()
+ Do exhaustive when (store.state.getMediaStateForSession(tab.id)) {
+ MediaState.State.PAUSED -> {
+ showAndEnable()
+ contentDescription =
+ context.getString(R.string.mozac_feature_media_notification_action_play)
+ setImageDrawable(
+ AppCompatResources.getDrawable(context, R.drawable.media_state_play)
+ )
+ }
- playPauseButtonView.setOnClickListener {
- Do exhaustive when (store.state.getMediaStateForSession(tab.id)) {
- MediaState.State.PLAYING -> {
- metrics.track(Event.TabMediaPause)
- store.state.media.pauseIfPlaying()
- }
+ MediaState.State.PLAYING -> {
+ showAndEnable()
+ contentDescription =
+ context.getString(R.string.mozac_feature_media_notification_action_pause)
+ setImageDrawable(
+ AppCompatResources.getDrawable(context, R.drawable.media_state_pause)
+ )
+ }
- MediaState.State.PAUSED -> {
- metrics.track(Event.TabMediaPlay)
- store.state.media.playIfPaused()
+ MediaState.State.NONE -> {
+ removeTouchDelegate()
+ removeAndDisable()
+ }
}
+ }
+
+ playPauseButtonView.setOnClickListener {
+ Do exhaustive when (store.state.getMediaStateForSession(tab.id)) {
+ MediaState.State.PLAYING -> {
+ metrics.track(Event.TabMediaPause)
+ store.state.media.pauseIfPlaying()
+ }
- MediaState.State.NONE -> throw AssertionError(
- "Play/Pause button clicked without play/pause state."
- )
+ MediaState.State.PAUSED -> {
+ metrics.track(Event.TabMediaPlay)
+ store.state.media.playIfPaused()
+ }
+
+ MediaState.State.NONE -> throw AssertionError(
+ "Play/Pause button clicked without play/pause state."
+ )
+ }
}
}
diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessorTest.kt
index 977e737f61..7dc14cc991 100644
--- a/app/src/test/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessorTest.kt
+++ b/app/src/test/java/org/mozilla/fenix/home/intent/OpenSpecificTabIntentProcessorTest.kt
@@ -14,12 +14,14 @@ import io.mockk.verifyOrder
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.feature.media.service.AbstractMediaService
+import mozilla.components.feature.media.service.AbstractMediaSessionService
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.BrowserDirection
+import org.mozilla.fenix.FeatureFlags.newMediaSessionApi
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@@ -78,8 +80,13 @@ class OpenSpecificTabIntentProcessorTest {
@Test
fun `GIVEN an intent with correct action and extra string, WHEN it is processed, THEN session should be selected and openToBrowser should be called`() {
val intent = Intent().apply {
- action = AbstractMediaService.Companion.ACTION_SWITCH_TAB
- putExtra(AbstractMediaService.Companion.EXTRA_TAB_ID, TEST_SESSION_ID)
+ if (newMediaSessionApi) {
+ action = AbstractMediaSessionService.Companion.ACTION_SWITCH_TAB
+ putExtra(AbstractMediaSessionService.Companion.EXTRA_TAB_ID, TEST_SESSION_ID)
+ } else {
+ action = AbstractMediaService.Companion.ACTION_SWITCH_TAB
+ putExtra(AbstractMediaService.Companion.EXTRA_TAB_ID, TEST_SESSION_ID)
+ }
}
val sessionManager: SessionManager = mockk(relaxed = true)
val session: Session = mockk(relaxed = true)
diff --git a/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayViewHolderTest.kt b/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayViewHolderTest.kt
index f1672e82e9..f55d4d6ccc 100644
--- a/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayViewHolderTest.kt
+++ b/app/src/test/java/org/mozilla/fenix/tabtray/TabTrayViewHolderTest.kt
@@ -15,17 +15,23 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.verify
import mozilla.components.browser.state.state.BrowserState
+import mozilla.components.browser.state.state.ContentState
+import mozilla.components.browser.state.state.MediaSessionState
import mozilla.components.browser.state.state.MediaState
+import mozilla.components.browser.state.state.SessionState
+import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.browser.toolbar.MAX_URI_LENGTH
import mozilla.components.concept.tabstray.Tab
import mozilla.components.concept.base.images.ImageLoadRequest
import mozilla.components.concept.base.images.ImageLoader
+import mozilla.components.concept.engine.mediasession.MediaSession
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mozilla.fenix.FeatureFlags.newMediaSessionApi
import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@@ -36,6 +42,8 @@ class TabTrayViewHolderTest {
private lateinit var view: View
@MockK private lateinit var imageLoader: ImageLoader
@MockK private lateinit var store: BrowserStore
+ @MockK private lateinit var sessionState: SessionState
+ @MockK private lateinit var mediaSessionState: MediaSessionState
@MockK private lateinit var metrics: MetricController
private var state = BrowserState()
@@ -74,14 +82,33 @@ class TabTrayViewHolderTest {
id = "123",
url = "https://example.com"
)
- state = state.copy(
- media = MediaState(
- aggregate = MediaState.Aggregate(
- activeTabId = "123",
- state = MediaState.State.PAUSED
+
+ if (newMediaSessionApi) {
+ state = state.copy(
+ tabs = listOf(
+ TabSessionState(
+ id = "123",
+ content = ContentState(
+ url = "https://example.com",
+ searchTerms = "search terms"
+ ),
+ mediaSessionState = mediaSessionState
+ )
)
)
- )
+
+ every { mediaSessionState.playbackState } answers { MediaSession.PlaybackState.PAUSED }
+ } else {
+ state = state.copy(
+ media = MediaState(
+ aggregate = MediaState.Aggregate(
+ activeTabId = "123",
+ state = MediaState.State.PAUSED
+ )
+ )
+ )
+ }
+
tabViewHolder.bind(tab, false, mockk(), mockk())
assertEquals("Play", playPauseButtonView.contentDescription)
@@ -96,14 +123,33 @@ class TabTrayViewHolderTest {
id = "123",
url = "https://example.com"
)
- state = state.copy(
- media = MediaState(
- aggregate = MediaState.Aggregate(
- activeTabId = "123",
- state = MediaState.State.PLAYING
+
+ if (newMediaSessionApi) {
+ state = state.copy(
+ tabs = listOf(
+ TabSessionState(
+ id = "123",
+ content = ContentState(
+ url = "https://example.com",
+ searchTerms = "search terms"
+ ),
+ mediaSessionState = mediaSessionState
+ )
)
)
- )
+
+ every { mediaSessionState.playbackState } answers { MediaSession.PlaybackState.PLAYING }
+ } else {
+ state = state.copy(
+ media = MediaState(
+ aggregate = MediaState.Aggregate(
+ activeTabId = "123",
+ state = MediaState.State.PLAYING
+ )
+ )
+ )
+ }
+
tabViewHolder.bind(tab, false, mockk(), mockk())
assertEquals("Pause", playPauseButtonView.contentDescription)