Commit 061cebc5 authored by Mugurell's avatar Mugurell Committed by mergify[bot]
Browse files

For #10470 - Add a new lastMediaAccess property for TabSessionState

This new property will be a timestamp of the last time media started playing in
the current page or be 0 if media hadn't started playing or another page loaded
in this tab so media was stopped.

To observe the media changes and update this property LastMediaAccessMiddleware
will have to be set on BrowserStore.
parent 6533d0db
......@@ -232,7 +232,7 @@ sealed class UndoAction : BrowserAction() {
*/
sealed class LastAccessAction : BrowserAction() {
/**
* Updates the timestamp of the [TabSessionState] with the given [tabId].
* Updates the [TabSessionState.lastAccess] timestamp of the tab with the given [tabId].
*
* @property tabId the ID of the tab to update.
* @property lastAccess the value to signify when the tab was last accessed; defaults to [System.currentTimeMillis].
......@@ -241,6 +241,20 @@ sealed class LastAccessAction : BrowserAction() {
val tabId: String,
val lastAccess: Long = System.currentTimeMillis()
) : LastAccessAction()
/**
* Updates [TabSessionState.lastMediaAccess] for when media started playing in the tab identified by [tabId]
* or [MediaSession] is destroyed
*
* @property tabId the ID of the tab to update.
* @property lastMediaAccess the value to signify when the tab last started playing media.
* Defaults to [System.currentTimeMillis].
* Use [0] to indicate media state of the current tab is reset (eg: when web document changed).
*/
data class UpdateLastMediaAccessAction(
val tabId: String,
val lastMediaAccess: Long = System.currentTimeMillis()
) : LastAccessAction()
}
/**
......
......@@ -4,7 +4,6 @@
package mozilla.components.browser.state.reducer
import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.ContainerAction
import mozilla.components.browser.state.action.ContentAction
......@@ -15,6 +14,7 @@ import mozilla.components.browser.state.action.EngineAction
import mozilla.components.browser.state.action.HistoryMetadataAction
import mozilla.components.browser.state.action.InitAction
import mozilla.components.browser.state.action.LastAccessAction
import mozilla.components.browser.state.action.LocaleAction
import mozilla.components.browser.state.action.MediaSessionAction
import mozilla.components.browser.state.action.ReaderAction
import mozilla.components.browser.state.action.RecentlyClosedAction
......@@ -25,7 +25,7 @@ import mozilla.components.browser.state.action.SystemAction
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.action.TrackingProtectionAction
import mozilla.components.browser.state.action.UndoAction
import mozilla.components.browser.state.action.LocaleAction
import mozilla.components.browser.state.action.WebExtensionAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.CustomTabSessionState
import mozilla.components.browser.state.state.SessionState
......
......@@ -6,6 +6,7 @@ package mozilla.components.browser.state.reducer
import mozilla.components.browser.state.action.LastAccessAction
import mozilla.components.browser.state.action.LastAccessAction.UpdateLastAccessAction
import mozilla.components.browser.state.action.LastAccessAction.UpdateLastMediaAccessAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
......@@ -21,5 +22,11 @@ internal object LastAccessReducer {
tabSessionState.copy(lastAccess = action.lastAccess)
}
}
is UpdateLastMediaAccessAction -> {
state.updateTabState(action.tabId) { sessionState ->
val tabSessionState = sessionState as TabSessionState
tabSessionState.copy(lastMediaAccess = action.lastMediaAccess)
}
}
}
}
......@@ -7,8 +7,8 @@ package mozilla.components.browser.state.state
import android.graphics.Bitmap
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineSessionState
import mozilla.components.concept.storage.HistoryMetadataKey
import mozilla.components.concept.engine.manifest.WebAppManifest
import mozilla.components.concept.storage.HistoryMetadataKey
import java.util.UUID
/**
......@@ -25,7 +25,10 @@ import java.util.UUID
* that contains the overridden values for this tab.
* @property readerState the [ReaderState] of this tab.
* @property contextId the session context ID of this tab.
* @param lastAccess The last time this tab was selected (requires LastAccessMiddleware).
* @property lastAccess The last time this tab was selected (requires LastAccessMiddleware).
* @property lastMediaAccess The last time media started playing in the current web document.
* Defaults to [0] if media hasn't started playing.
* Requires [LastMediaAccessMiddleware] to update the value when playback starts.
*/
data class TabSessionState(
override val id: String = UUID.randomUUID().toString(),
......@@ -38,6 +41,7 @@ data class TabSessionState(
override val source: SessionState.Source = SessionState.Source.NONE,
val parentId: String? = null,
val lastAccess: Long = 0L,
val lastMediaAccess: Long = 0L,
val readerState: ReaderState = ReaderState(),
val historyMetadata: HistoryMetadataKey? = null
) : SessionState {
......@@ -77,6 +81,7 @@ fun createTab(
thumbnail: Bitmap? = null,
contextId: String? = null,
lastAccess: Long = 0L,
lastMediaAccess: Long = 0L,
source: SessionState.Source = SessionState.Source.NONE,
engineSession: EngineSession? = null,
engineSessionState: EngineSessionState? = null,
......@@ -102,6 +107,7 @@ fun createTab(
readerState = readerState,
contextId = contextId,
lastAccess = lastAccess,
lastMediaAccess = lastMediaAccess,
source = source,
engineState = EngineState(
engineSession = engineSession,
......
/* 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 mozilla.components.browser.state.reducer
import mozilla.components.browser.state.action.LastAccessAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Test
class LastAccessReducerTest {
@Test
fun `WHEN the reducer is called for UpdateLastAccessAction THEN a new state with updated lastAccess is returned`() {
val tab1 = TabSessionState(id = "tab1", lastAccess = 111, content = mock())
val tab2 = TabSessionState(id = "tab2", lastAccess = 222, content = mock())
val browserState = BrowserState(tabs = listOf(tab1, tab2))
val updatedState = LastAccessReducer.reduce(
browserState, LastAccessAction.UpdateLastAccessAction(tabId = "tab1", lastAccess = 345)
)
assertEquals(2, updatedState.tabs.size)
assertEquals(345, updatedState.tabs[0].lastAccess)
assertEquals(222, updatedState.tabs[1].lastAccess)
}
@Test
fun `WHEN the reducer is called for UpdateLastMediaAccessAction THEN a new state with updated lastMediaAccess is returned`() {
val tab1 = TabSessionState(id = "tab1", lastMediaAccess = 111, content = mock())
val tab2 = TabSessionState(id = "tab2", lastMediaAccess = 222, content = mock())
val browserState = BrowserState(tabs = listOf(tab1, tab2))
val updatedState = LastAccessReducer.reduce(
browserState, LastAccessAction.UpdateLastMediaAccessAction(tabId = "tab1", lastMediaAccess = 345)
)
assertEquals(2, updatedState.tabs.size)
assertEquals(345, updatedState.tabs[0].lastMediaAccess)
assertEquals(222, updatedState.tabs[1].lastMediaAccess)
}
}
/* 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 mozilla.components.feature.media.middleware
import mozilla.components.browser.state.action.BrowserAction
import mozilla.components.browser.state.action.LastAccessAction
import mozilla.components.browser.state.action.MediaSessionAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.concept.engine.mediasession.MediaSession
import mozilla.components.lib.state.Middleware
import mozilla.components.lib.state.MiddlewareContext
/**
* [Middleware] that updates [TabSessionState.lastMediaAccess] everytime the user start playing media
* or navigates to another page in the last tab that was playing media.
*/
class LastMediaAccessMiddleware : Middleware<BrowserState, BrowserAction> {
@Suppress("ComplexCondition")
override fun invoke(
context: MiddlewareContext<BrowserState, BrowserAction>,
next: (BrowserAction) -> Unit,
action: BrowserAction
) {
next(action)
if (action is MediaSessionAction.UpdateMediaPlaybackStateAction &&
action.playbackState == MediaSession.PlaybackState.PLAYING
) {
context.dispatch(LastAccessAction.UpdateLastMediaAccessAction(action.tabId))
} else if (action is MediaSessionAction.DeactivatedMediaSessionAction) {
context.dispatch(LastAccessAction.UpdateLastMediaAccessAction(action.tabId, 0))
}
}
}
/* 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 mozilla.components.feature.media.middleware
import mozilla.components.browser.state.action.MediaSessionAction
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.ContentState
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.mediasession.MediaSession
import mozilla.components.support.test.ext.joinBlocking
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class LastMediaAccessMiddlewareTest {
@Test
fun `GIVEN a normal tab WHEN media started playing THEN then lastMediaAccess is updated`() {
val tabId = "42"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(content = ContentState("https://mozilla.org/1", private = true)),
TabSessionState(
content = ContentState("https://mozilla.org/2", private = false),
id = tabId
),
TabSessionState(content = ContentState("https://mozilla.org/3", private = false))
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.PLAYING))
.joinBlocking()
val updatesLastMediaAccess = store.state.tabs[1].lastMediaAccess
assertTrue("expected lastMediaAccess ($updatesLastMediaAccess) > 0", updatesLastMediaAccess > 0)
}
@Test
fun `GIVEN a private tab WHEN media started playing THEN then lastMediaAccess is updated`() {
val tabId = "43"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(content = ContentState("https://mozilla.org/1", private = true)),
TabSessionState(
content = ContentState("https://mozilla.org/2", private = true),
id = tabId
),
TabSessionState(content = ContentState("https://mozilla.org/3", private = false))
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.PLAYING))
.joinBlocking()
val updatesLastMediaAccess = store.state.tabs[1].lastMediaAccess
assertTrue("expected lastMediaAccess ($updatesLastMediaAccess) > 0", updatesLastMediaAccess > 0)
}
@Test
fun `GIVEN a normal tab WHEN media is paused THEN then lastMediaAccess is not changed`() {
val tabId = "42"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = false),
id = tabId,
lastMediaAccess = 222
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.PAUSED))
.joinBlocking()
assertEquals(222, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN a private tab WHEN media is paused THEN then lastMediaAccess is not changed`() {
val tabId = "43"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = true),
id = tabId,
lastMediaAccess = 333
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.PAUSED))
.joinBlocking()
assertEquals(333, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN a normal tab WHEN media is stopped THEN then lastMediaAccess is not changed`() {
val tabId = "42"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = false),
id = tabId,
lastMediaAccess = 222
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.STOPPED))
.joinBlocking()
assertEquals(222, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN a private tab WHEN media is stopped THEN then lastMediaAccess is not changed`() {
val tabId = "43"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = true),
id = tabId,
lastMediaAccess = 333
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.STOPPED))
.joinBlocking()
assertEquals(333, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN a normal tab WHEN media status is unknown THEN then lastMediaAccess is not changed`() {
val tabId = "42"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = false),
id = tabId,
lastMediaAccess = 222
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.UNKNOWN))
.joinBlocking()
assertEquals(222, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN a private tab WHEN media status is unknown THEN then lastMediaAccess is not changed`() {
val tabId = "43"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = true),
id = tabId,
lastMediaAccess = 333
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.UpdateMediaPlaybackStateAction(tabId, MediaSession.PlaybackState.UNKNOWN))
.joinBlocking()
assertEquals(333, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN lastMediaAccess is set for a normal tab WHEN media session is deactivated THEN reset lastMediaAccess to 0`() {
val tabId = "42"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = false),
id = tabId,
lastMediaAccess = 222
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.DeactivatedMediaSessionAction(tabId))
.joinBlocking()
assertEquals(0, store.state.tabs[0].lastMediaAccess)
}
@Test
fun `GIVEN lastMediaAccess is set for a private tab WHEN media session is deactivated THEN reset lastMediaAccess to 0`() {
val tabId = "43"
val browserState = BrowserState(
tabs = listOf(
TabSessionState(
content = ContentState("https://mozilla.org/2", private = true),
id = tabId,
lastMediaAccess = 333
)
)
)
val store = BrowserStore(
initialState = browserState,
middleware = listOf(LastMediaAccessMiddleware())
)
store
.dispatch(MediaSessionAction.DeactivatedMediaSessionAction(tabId))
.joinBlocking()
assertEquals(0, store.state.tabs[0].lastMediaAccess)
}
}
......@@ -11,6 +11,9 @@ permalink: /changelog/
* [Gecko](https://github.com/mozilla-mobile/android-components/blob/master/buildSrc/src/main/java/Gecko.kt)
* [Configuration](https://github.com/mozilla-mobile/android-components/blob/master/.config.yml)
* **browser-state**:
* 🌟️ Adds a new `lastMediaAccess` in `TabSessionState` as an easy way to check the timestamp of when media last started playing on a particular webpage. The value will be 0 if no media was started. To observe the media playback and updating this property one needs to add a new `LastMediaAccessMiddleware` to `BrowserStore`.
* **feature-search**
* Updated the icon of the bing search engine.
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment