bring pwa to front when already running instead of starting a new instance

This commit is contained in:
Fabian Freund
2026-04-28 16:27:54 +02:00
parent bf94e1e0fe
commit 65c89c6c65
2 changed files with 135 additions and 5 deletions
@@ -45,11 +45,21 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
context: Context,
customTabSessionId: String,
webAppManifestUrl: String? = null,
pwaProfileUuid: String? = null,
pwaContextId: String? = null,
pwaToken: String? = null,
pwaInstallStartUrl: String? = null,
): Intent {
return Intent(context, ExternalAppBrowserActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, customTabSessionId)
webAppManifestUrl?.let { putExtra(EXTRA_WEB_APP_MANIFEST_URL, it) }
pwaProfileUuid?.let { putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, it) }
pwaContextId?.let { putExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID, it) }
pwaToken?.let { putExtra(PwaConstants.EXTRA_PWA_TOKEN, it) }
pwaInstallStartUrl?.let {
putExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL, it)
}
}
}
}
@@ -7,7 +7,7 @@
package eu.weblibre.flutter_mozilla_components.activities
import android.app.Activity
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.pm.ShortcutManager
@@ -15,6 +15,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.Log
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.weblibre.flutter_mozilla_components.Components
import eu.weblibre.flutter_mozilla_components.GlobalComponents
import eu.weblibre.flutter_mozilla_components.PwaConstants
@@ -26,6 +27,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mozilla.components.browser.state.selector.findCustomTab
import mozilla.components.feature.customtabs.CustomTabIntentProcessor
import mozilla.components.feature.intent.ext.getSessionId
import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
@@ -168,7 +170,7 @@ class IntentReceiverActivity : Activity() {
}
Log.d(TAG, "Trusted PWA intent with profile metadata: profileUuid=$profileUuid, contextId=$contextId")
handlePwaIntent(intent, profileUuid, contextId)
handlePwaIntent(intent, profileUuid, contextId, token)
return
}
@@ -345,6 +347,7 @@ class IntentReceiverActivity : Activity() {
intent: Intent,
profileUuid: String,
contextId: String?,
token: String?,
) {
val url = intent.dataString
if (url == null) {
@@ -353,18 +356,36 @@ class IntentReceiverActivity : Activity() {
return
}
val installStartUrl = intent.getStringExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL)
val currentProfileUuid = getCurrentProfileUuid()
if (currentProfileUuid != null && currentProfileUuid != profileUuid) {
Log.d(TAG, "Profile mismatch: current=$currentProfileUuid, expected=$profileUuid")
showProfileMismatchDialog(
intent = intent,
onProceed = { launchPwaWithContext(url, contextId) },
onProceed = {
launchPwaWithContext(
url = url,
contextId = contextId,
profileUuid = profileUuid,
token = token,
installStartUrl = installStartUrl,
allowTaskReuse = false,
)
},
isPwa = true,
)
} else {
Log.d(TAG, "Profile match or indeterminate, launching PWA with contextId=$contextId")
launchPwaWithContext(url, contextId)
launchPwaWithContext(
url = url,
contextId = contextId,
profileUuid = profileUuid,
token = token,
installStartUrl = installStartUrl,
allowTaskReuse = true,
)
}
}
@@ -441,7 +462,19 @@ class IntentReceiverActivity : Activity() {
/**
* Launches the PWA with the specified context ID for storage isolation.
*/
private fun launchPwaWithContext(url: String, contextId: String?) {
private fun launchPwaWithContext(
url: String,
contextId: String?,
profileUuid: String,
token: String?,
installStartUrl: String?,
allowTaskReuse: Boolean,
) {
if (allowTaskReuse && bringPwaTaskToFront(url, profileUuid, contextId, token, installStartUrl)) {
finish()
return
}
val components = GlobalComponents.components
?: run {
Log.e(TAG, "Components not available for PWA launch")
@@ -467,6 +500,10 @@ class IntentReceiverActivity : Activity() {
context = this@IntentReceiverActivity,
customTabSessionId = sessionId,
webAppManifestUrl = url,
pwaProfileUuid = profileUuid,
pwaContextId = contextId,
pwaToken = token,
pwaInstallStartUrl = installStartUrl ?: url,
)
startActivity(externalIntent)
finish()
@@ -477,6 +514,89 @@ class IntentReceiverActivity : Activity() {
}
}
private fun bringPwaTaskToFront(
url: String,
profileUuid: String,
contextId: String?,
token: String?,
installStartUrl: String?,
): Boolean {
if (token.isNullOrEmpty()) {
return false
}
val activityManager = getSystemService(ActivityManager::class.java) ?: return false
val components = GlobalComponents.components
for (appTask in activityManager.appTasks) {
val baseIntent = runCatching { appTask.taskInfo.baseIntent }
.getOrElse { error ->
Log.w(TAG, "Failed to inspect app task", error)
return@getOrElse null
} ?: continue
if (baseIntent.component?.className != ExternalAppBrowserActivity::class.java.name) {
continue
}
if (!matchesPwaTaskIntent(baseIntent, url, profileUuid, contextId, token, installStartUrl, components)) {
continue
}
return try {
appTask.moveToFront()
Log.d(TAG, "Resumed existing PWA task for $url")
true
} catch (e: Exception) {
Log.e(TAG, "Failed to move existing PWA task to front", e)
false
}
}
return false
}
private fun matchesPwaTaskIntent(
baseIntent: Intent,
url: String,
profileUuid: String,
contextId: String?,
token: String,
installStartUrl: String?,
components: Components?,
): Boolean {
val requestedContextId = contextId.orEmpty()
val requestedInstallStartUrl = installStartUrl ?: url
val taskToken = baseIntent.getStringExtra(PwaConstants.EXTRA_PWA_TOKEN)
if (!taskToken.isNullOrEmpty()) {
return taskToken == token &&
baseIntent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID) == profileUuid &&
baseIntent.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID).orEmpty() == requestedContextId &&
baseIntent.getStringExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL) == requestedInstallStartUrl
}
val taskManifestUrl = baseIntent.getStringExtra(ExternalAppBrowserActivity.EXTRA_WEB_APP_MANIFEST_URL)
if (taskManifestUrl != requestedInstallStartUrl && taskManifestUrl != url) {
return false
}
val sessionId = baseIntent.getStringExtra(ExternalAppBrowserActivity.EXTRA_CUSTOM_TAB_SESSION_ID)
?: return false
val customTab = components?.core?.store?.state?.findCustomTab(sessionId) ?: return false
val appType = customTab.config.externalAppType
val isPwa = appType == ExternalAppType.PROGRESSIVE_WEB_APP ||
appType == ExternalAppType.TRUSTED_WEB_ACTIVITY
if (!isPwa) {
return false
}
val manifestStartUrl = customTab.content.webAppManifest?.startUrl
val launchUrl = manifestStartUrl ?: taskManifestUrl ?: customTab.content.url
return customTab.contextId.orEmpty() == requestedContextId &&
(launchUrl == requestedInstallStartUrl || launchUrl == url)
}
/**
* Creates a custom tab session for a PWA with the specified context ID.
*/