Revision control
Copy as Markdown
/* 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
package org.mozilla.tv.firefox.session
import android.graphics.Bitmap
import android.net.Uri
import androidx.annotation.AnyThread
import io.reactivex.Observable
import io.reactivex.subjects.BehaviorSubject
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.Subject
import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager
import mozilla.components.feature.session.SessionUseCases
import org.mozilla.tv.firefox.ext.isYoutubeTV
import org.mozilla.tv.firefox.ext.toUri
import org.mozilla.tv.firefox.telemetry.TelemetryIntegration
import org.mozilla.tv.firefox.utils.TurboMode
import org.mozilla.tv.firefox.webrender.EngineViewCache
/**
* Repository that is responsible for storing state related to the browser.
*/
class SessionRepo(
private val sessionManager: SessionManager,
private val sessionUseCases: SessionUseCases,
private val turboMode: TurboMode
) {
data class State(
val backEnabled: Boolean,
val forwardEnabled: Boolean,
val desktopModeActive: Boolean,
val turboModeActive: Boolean,
val currentUrl: String,
val loading: Boolean
)
enum class Event {
YouTubeBack, ExitYouTube
}
private val _state: BehaviorSubject<State> = BehaviorSubject.create()
val state: Observable<State> = _state.hide()
private val _events: Subject<Event> = PublishSubject.create()
val events: Observable<Event> = _events.hide()
var canGoBackTwice: (() -> Boolean?)? = null
private var previousURLHost: String? = null
fun observeSources() {
SessionObserverHelper.attach(this, sessionManager)
turboMode.observable.observeForever { update() }
}
@AnyThread
fun update() {
session?.let { session ->
fun isHostDifferentFromPrevious(): Boolean {
val currentURLHost = session.url.toUri()?.host ?: return true
return (previousURLHost != currentURLHost).also {
previousURLHost = currentURLHost
}
}
fun disableDesktopMode() {
setDesktopMode(false)
session.url.toUri()?.let { loadURL(it) }
}
fun causeSideEffects() {
if (isHostDifferentFromPrevious() && session.desktopMode) {
disableDesktopMode()
}
}
fun <T> BehaviorSubject<T>.onNextIfNew(value: T) {
if (this.value != value) this.onNext(value)
}
causeSideEffects()
val newState = State(
// The menu back button should not be enabled if the previous screen was our initial url (home)
backEnabled = canGoBackTwice?.invoke() ?: false,
forwardEnabled = session.canGoForward,
desktopModeActive = session.desktopMode,
turboModeActive = turboMode.isEnabled,
currentUrl = session.url,
loading = session.loading
)
_state.onNextIfNew(newState)
}
}
fun currentURLScreenshot(): Bitmap? = session?.thumbnail
/**
* @param forceYouTubeExit if true while YouTube is active, back out of the
* site instead of moving focus
*
* @Returns true if the event was consumed
*/
fun attemptBack(forceYouTubeExit: Boolean = false): Boolean {
val session = session ?: return false
if (session.isYoutubeTV && forceYouTubeExit) {
_events.onNext(Event.ExitYouTube)
return true
}
if (session.isYoutubeTV && !forceYouTubeExit) {
_events.onNext(Event.YouTubeBack)
return true
}
if (session.canGoBack) {
exitFullScreenIfPossible()
sessionUseCases.goBack.invoke()
TelemetryIntegration.INSTANCE.browserBackControllerEvent()
return true
}
return false
}
fun goForward() {
if (session?.canGoForward == true) sessionUseCases.goForward.invoke()
}
fun reload() = sessionUseCases.reload.invoke()
fun setDesktopMode(active: Boolean) = session?.let { it.desktopMode = active }
/**
* Causes [state] to emit its most recently pushed value. This can be used
* to reset UI that has been adjusted by the user (e.g., EditText text)
*/
fun pushCurrentValue() = _state.onNext(_state.value!!) // TODO does this do anything? If not,
// we can have state.distinctUntilChanged and get rid of postIfNew
fun loadURL(url: Uri) = session?.let { sessionManager.getEngineSession(it)?.loadUrl(url.toString()) }
fun setTurboModeEnabled(enabled: Boolean) {
turboMode.isEnabled = enabled
}
private val session: Session? get() = sessionManager.selectedSession
fun clearBrowsingData(engineViewCache: EngineViewCache) {
sessionManager.getEngineSession()?.clearData() // Only works for [SystemEngineView]
sessionManager.removeAll()
engineViewCache.doNotPersist()
}
/**
* Returns true if fullscreen was exited
*/
fun exitFullScreenIfPossible(): Boolean {
if (session?.fullScreenMode == true) {
// Changing the URL while full-screened can lead to unstable behavior
// (see #1224 and #1719), so we always attempt to exit full-screen
// before doing so
sessionManager.getEngineSession()?.exitFullScreenMode()
return true
}
return false
}
}