improved pwa lifecycle
This commit is contained in:
+3
-1
@@ -163,7 +163,9 @@ object GlobalComponents {
|
|||||||
|
|
||||||
val previousComponents = _components
|
val previousComponents = _components
|
||||||
val previousMode = currentMode
|
val previousMode = currentMode
|
||||||
val previousCustomTabs = if (previousMode == ComponentsMode.EXTERNAL) {
|
val isSameProfile = previousComponents?.profileApplicationContext?.relativePath ==
|
||||||
|
applicationContext.relativePath
|
||||||
|
val previousCustomTabs = if (isSameProfile) {
|
||||||
previousComponents?.core?.store?.state?.customTabs.orEmpty()
|
previousComponents?.core?.store?.state?.customTabs.orEmpty()
|
||||||
} else {
|
} else {
|
||||||
emptyList()
|
emptyList()
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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 https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package eu.weblibre.flutter_mozilla_components
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import mozilla.components.browser.state.action.CustomTabListAction
|
||||||
|
import mozilla.components.browser.state.state.CustomTabConfig
|
||||||
|
import mozilla.components.browser.state.state.ExternalAppType
|
||||||
|
import mozilla.components.browser.state.state.SessionState
|
||||||
|
import mozilla.components.browser.state.state.createCustomTab
|
||||||
|
import mozilla.components.concept.engine.EngineSession
|
||||||
|
|
||||||
|
object PwaSessionCreator {
|
||||||
|
suspend fun create(url: String, contextId: String?): String {
|
||||||
|
val components = GlobalComponents.components
|
||||||
|
?: throw IllegalStateException("Components not initialized")
|
||||||
|
|
||||||
|
val manifest = withContext(Dispatchers.IO) {
|
||||||
|
components.core.webAppManifestStorage.loadManifest(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
return withContext(Dispatchers.Main) {
|
||||||
|
val customTabConfig = CustomTabConfig(
|
||||||
|
externalAppType = ExternalAppType.PROGRESSIVE_WEB_APP,
|
||||||
|
)
|
||||||
|
|
||||||
|
val tab = createCustomTab(
|
||||||
|
url = url,
|
||||||
|
contextId = contextId,
|
||||||
|
config = customTabConfig,
|
||||||
|
webAppManifest = manifest,
|
||||||
|
source = SessionState.Source.Internal.CustomTab,
|
||||||
|
private = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
components.core.store.dispatch(
|
||||||
|
CustomTabListAction.AddCustomTabAction(tab),
|
||||||
|
)
|
||||||
|
|
||||||
|
val loadUrlFlags = EngineSession.LoadUrlFlags.external()
|
||||||
|
components.useCases.sessionUseCases.loadUrl(url, tab.id, loadUrlFlags)
|
||||||
|
|
||||||
|
tab.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
-6
@@ -16,6 +16,7 @@ import androidx.core.view.WindowCompat
|
|||||||
import eu.weblibre.flutter_mozilla_components.ExternalAppBrowserFragment
|
import eu.weblibre.flutter_mozilla_components.ExternalAppBrowserFragment
|
||||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||||
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
||||||
|
import eu.weblibre.flutter_mozilla_components.PwaSessionCreator
|
||||||
import eu.weblibre.flutter_mozilla_components.R
|
import eu.weblibre.flutter_mozilla_components.R
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -66,6 +67,7 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
private val logger = Logger("ExternalAppBrowserActivity")
|
private val logger = Logger("ExternalAppBrowserActivity")
|
||||||
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
private val coroutineScope = CoroutineScope(Dispatchers.Main + SupervisorJob())
|
||||||
|
private var isRecoveringPwaSession = false
|
||||||
|
|
||||||
private val customTabSessionId: String?
|
private val customTabSessionId: String?
|
||||||
get() = intent?.getStringExtra(EXTRA_CUSTOM_TAB_SESSION_ID)
|
get() = intent?.getStringExtra(EXTRA_CUSTOM_TAB_SESSION_ID)
|
||||||
@@ -85,17 +87,19 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
finishAndRemoveTask()
|
finishAndRemoveTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
setContentView(R.layout.activity_external_app_browser)
|
||||||
|
|
||||||
val sessionId = customTabSessionId
|
val sessionId = customTabSessionId
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
Log.e(TAG, "No custom tab session ID provided")
|
Log.e(TAG, "No custom tab session ID provided")
|
||||||
logger.error("No custom tab session ID provided, finishing.")
|
logger.error("No custom tab session ID provided, finishing.")
|
||||||
fallbackToMainActivity()
|
if (!recoverPwaSession(null, "missing session ID")) {
|
||||||
|
fallbackToMainActivity()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
setContentView(R.layout.activity_external_app_browser)
|
|
||||||
|
|
||||||
val components = GlobalComponents.components
|
val components = GlobalComponents.components
|
||||||
if (components == null) {
|
if (components == null) {
|
||||||
if (GlobalComponents.ensureExternalComponents(applicationContext)) {
|
if (GlobalComponents.ensureExternalComponents(applicationContext)) {
|
||||||
@@ -145,7 +149,9 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
if (components.core.store.state.findCustomTab(sessionId) == null) {
|
if (components.core.store.state.findCustomTab(sessionId) == null) {
|
||||||
Log.e(TAG, "Custom tab session $sessionId not found in store")
|
Log.e(TAG, "Custom tab session $sessionId not found in store")
|
||||||
logger.error("Custom tab session $sessionId not found in store, finishing.")
|
logger.error("Custom tab session $sessionId not found in store, finishing.")
|
||||||
fallbackToMainActivity()
|
if (!recoverPwaSession(sessionId, "session not found in store")) {
|
||||||
|
fallbackToMainActivity()
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,10 +174,61 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
if (components.core.store.state.findCustomTab(sessionId) == null) {
|
if (components.core.store.state.findCustomTab(sessionId) == null) {
|
||||||
Log.w(TAG, "Custom tab session $sessionId gone on resume")
|
Log.w(TAG, "Custom tab session $sessionId gone on resume")
|
||||||
logger.debug("Custom tab session $sessionId gone, finishing activity.")
|
logger.debug("Custom tab session $sessionId gone, finishing activity.")
|
||||||
fallbackToMainActivity()
|
if (!recoverPwaSession(sessionId, "session gone on resume")) {
|
||||||
|
fallbackToMainActivity()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun recoverPwaSession(missingSessionId: String?, reason: String): Boolean {
|
||||||
|
if (isRecoveringPwaSession) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
val launchUrl = webAppManifestUrl
|
||||||
|
?: intent?.getStringExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL)
|
||||||
|
?: return false
|
||||||
|
|
||||||
|
val isPwaTask = webAppManifestUrl != null ||
|
||||||
|
intent?.hasExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID) == true
|
||||||
|
if (!isPwaTask) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val contextId = intent?.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID)
|
||||||
|
isRecoveringPwaSession = true
|
||||||
|
Log.w(TAG, "Recovering PWA session for $launchUrl: $reason")
|
||||||
|
|
||||||
|
coroutineScope.launch {
|
||||||
|
try {
|
||||||
|
val sessionId = PwaSessionCreator.create(launchUrl, contextId)
|
||||||
|
Log.d(
|
||||||
|
TAG,
|
||||||
|
"Recovered PWA session: old=$missingSessionId, new=$sessionId, url=$launchUrl",
|
||||||
|
)
|
||||||
|
|
||||||
|
intent.putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, sessionId)
|
||||||
|
if (webAppManifestUrl == null) {
|
||||||
|
intent.putExtra(EXTRA_WEB_APP_MANIFEST_URL, launchUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFinishing && !isDestroyed) {
|
||||||
|
showFragment(sessionId)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to recover PWA session", e)
|
||||||
|
logger.error("Failed to recover PWA session, removing stale task.", e)
|
||||||
|
if (!isFinishing && !isDestroyed) {
|
||||||
|
finishAndRemoveTask()
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
isRecoveringPwaSession = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun fallbackToMainActivity() {
|
private fun fallbackToMainActivity() {
|
||||||
val mainIntent = Intent().apply {
|
val mainIntent = Intent().apply {
|
||||||
setClassName(this@ExternalAppBrowserActivity, "eu.weblibre.gecko.MainActivity")
|
setClassName(this@ExternalAppBrowserActivity, "eu.weblibre.gecko.MainActivity")
|
||||||
|
|||||||
+7
-47
@@ -19,6 +19,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import eu.weblibre.flutter_mozilla_components.Components
|
import eu.weblibre.flutter_mozilla_components.Components
|
||||||
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
import eu.weblibre.flutter_mozilla_components.GlobalComponents
|
||||||
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
import eu.weblibre.flutter_mozilla_components.PwaConstants
|
||||||
|
import eu.weblibre.flutter_mozilla_components.PwaSessionCreator
|
||||||
import eu.weblibre.flutter_mozilla_components.gatekeeper.IntentBlockNotifier
|
import eu.weblibre.flutter_mozilla_components.gatekeeper.IntentBlockNotifier
|
||||||
import eu.weblibre.flutter_mozilla_components.gatekeeper.IntentGatekeeperPreferences
|
import eu.weblibre.flutter_mozilla_components.gatekeeper.IntentGatekeeperPreferences
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -26,7 +27,6 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import mozilla.components.browser.state.selector.findCustomTab
|
import mozilla.components.browser.state.selector.findCustomTab
|
||||||
import mozilla.components.feature.customtabs.CustomTabIntentProcessor
|
import mozilla.components.feature.customtabs.CustomTabIntentProcessor
|
||||||
import mozilla.components.feature.intent.ext.getSessionId
|
import mozilla.components.feature.intent.ext.getSessionId
|
||||||
@@ -475,23 +475,17 @@ class IntentReceiverActivity : Activity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val components = GlobalComponents.components
|
if (GlobalComponents.components == null) {
|
||||||
?: run {
|
Log.e(TAG, "Components not available for PWA launch")
|
||||||
Log.e(TAG, "Components not available for PWA launch")
|
handleRegularIntent(intent)
|
||||||
handleRegularIntent(intent)
|
return
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
try {
|
try {
|
||||||
val manifest = withContext(Dispatchers.IO) {
|
val sessionId = PwaSessionCreator.create(
|
||||||
components.core.webAppManifestStorage.loadManifest(url)
|
|
||||||
}
|
|
||||||
|
|
||||||
val sessionId = createPwaSession(
|
|
||||||
url = url,
|
url = url,
|
||||||
contextId = contextId,
|
contextId = contextId,
|
||||||
manifest = manifest
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Log.d(TAG, "Created PWA session: contextId=$contextId, sessionId=$sessionId")
|
Log.d(TAG, "Created PWA session: contextId=$contextId, sessionId=$sessionId")
|
||||||
@@ -597,40 +591,6 @@ class IntentReceiverActivity : Activity() {
|
|||||||
(launchUrl == requestedInstallStartUrl || launchUrl == url)
|
(launchUrl == requestedInstallStartUrl || launchUrl == url)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a custom tab session for a PWA with the specified context ID.
|
|
||||||
*/
|
|
||||||
private fun createPwaSession(
|
|
||||||
url: String,
|
|
||||||
contextId: String?,
|
|
||||||
manifest: mozilla.components.concept.engine.manifest.WebAppManifest?
|
|
||||||
): String {
|
|
||||||
val components = GlobalComponents.components
|
|
||||||
?: throw IllegalStateException("Components not initialized")
|
|
||||||
|
|
||||||
val customTabConfig = mozilla.components.browser.state.state.CustomTabConfig(
|
|
||||||
externalAppType = mozilla.components.browser.state.state.ExternalAppType.PROGRESSIVE_WEB_APP
|
|
||||||
)
|
|
||||||
|
|
||||||
val tab = mozilla.components.browser.state.state.createCustomTab(
|
|
||||||
url = url,
|
|
||||||
contextId = contextId,
|
|
||||||
config = customTabConfig,
|
|
||||||
webAppManifest = manifest,
|
|
||||||
source = mozilla.components.browser.state.state.SessionState.Source.Internal.CustomTab,
|
|
||||||
private = false
|
|
||||||
)
|
|
||||||
|
|
||||||
components.core.store.dispatch(
|
|
||||||
mozilla.components.browser.state.action.CustomTabListAction.AddCustomTabAction(tab)
|
|
||||||
)
|
|
||||||
|
|
||||||
val loadUrlFlags = mozilla.components.concept.engine.EngineSession.LoadUrlFlags.external()
|
|
||||||
components.useCases.sessionUseCases.loadUrl(url, tab.id, loadUrlFlags)
|
|
||||||
|
|
||||||
return tab.id
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extracts a URL from a SEND intent.
|
* Extracts a URL from a SEND intent.
|
||||||
* First checks EXTRA_TEXT for a URL, then EXTRA_STREAM.
|
* First checks EXTRA_TEXT for a URL, then EXTRA_STREAM.
|
||||||
|
|||||||
Reference in New Issue
Block a user