bring pwa to front when already running instead of starting a new instance
This commit is contained in:
+10
@@ -45,11 +45,21 @@ open class ExternalAppBrowserActivity : AppCompatActivity() {
|
|||||||
context: Context,
|
context: Context,
|
||||||
customTabSessionId: String,
|
customTabSessionId: String,
|
||||||
webAppManifestUrl: String? = null,
|
webAppManifestUrl: String? = null,
|
||||||
|
pwaProfileUuid: String? = null,
|
||||||
|
pwaContextId: String? = null,
|
||||||
|
pwaToken: String? = null,
|
||||||
|
pwaInstallStartUrl: String? = null,
|
||||||
): Intent {
|
): Intent {
|
||||||
return Intent(context, ExternalAppBrowserActivity::class.java).apply {
|
return Intent(context, ExternalAppBrowserActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
|
||||||
putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, customTabSessionId)
|
putExtra(EXTRA_CUSTOM_TAB_SESSION_ID, customTabSessionId)
|
||||||
webAppManifestUrl?.let { putExtra(EXTRA_WEB_APP_MANIFEST_URL, it) }
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+125
-5
@@ -7,7 +7,7 @@
|
|||||||
package eu.weblibre.flutter_mozilla_components.activities
|
package eu.weblibre.flutter_mozilla_components.activities
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import android.app.ActivityManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ShortcutManager
|
import android.content.pm.ShortcutManager
|
||||||
@@ -15,6 +15,7 @@ import android.net.Uri
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
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
|
||||||
@@ -26,6 +27,7 @@ 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 kotlinx.coroutines.withContext
|
||||||
|
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
|
||||||
import mozilla.components.feature.pwa.intent.WebAppIntentProcessor
|
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")
|
Log.d(TAG, "Trusted PWA intent with profile metadata: profileUuid=$profileUuid, contextId=$contextId")
|
||||||
handlePwaIntent(intent, profileUuid, contextId)
|
handlePwaIntent(intent, profileUuid, contextId, token)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -345,6 +347,7 @@ class IntentReceiverActivity : Activity() {
|
|||||||
intent: Intent,
|
intent: Intent,
|
||||||
profileUuid: String,
|
profileUuid: String,
|
||||||
contextId: String?,
|
contextId: String?,
|
||||||
|
token: String?,
|
||||||
) {
|
) {
|
||||||
val url = intent.dataString
|
val url = intent.dataString
|
||||||
if (url == null) {
|
if (url == null) {
|
||||||
@@ -353,18 +356,36 @@ class IntentReceiverActivity : Activity() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val installStartUrl = intent.getStringExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL)
|
||||||
|
|
||||||
val currentProfileUuid = getCurrentProfileUuid()
|
val currentProfileUuid = getCurrentProfileUuid()
|
||||||
|
|
||||||
if (currentProfileUuid != null && currentProfileUuid != profileUuid) {
|
if (currentProfileUuid != null && currentProfileUuid != profileUuid) {
|
||||||
Log.d(TAG, "Profile mismatch: current=$currentProfileUuid, expected=$profileUuid")
|
Log.d(TAG, "Profile mismatch: current=$currentProfileUuid, expected=$profileUuid")
|
||||||
showProfileMismatchDialog(
|
showProfileMismatchDialog(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
onProceed = { launchPwaWithContext(url, contextId) },
|
onProceed = {
|
||||||
|
launchPwaWithContext(
|
||||||
|
url = url,
|
||||||
|
contextId = contextId,
|
||||||
|
profileUuid = profileUuid,
|
||||||
|
token = token,
|
||||||
|
installStartUrl = installStartUrl,
|
||||||
|
allowTaskReuse = false,
|
||||||
|
)
|
||||||
|
},
|
||||||
isPwa = true,
|
isPwa = true,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Profile match or indeterminate, launching PWA with contextId=$contextId")
|
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.
|
* 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
|
val components = GlobalComponents.components
|
||||||
?: run {
|
?: run {
|
||||||
Log.e(TAG, "Components not available for PWA launch")
|
Log.e(TAG, "Components not available for PWA launch")
|
||||||
@@ -467,6 +500,10 @@ class IntentReceiverActivity : Activity() {
|
|||||||
context = this@IntentReceiverActivity,
|
context = this@IntentReceiverActivity,
|
||||||
customTabSessionId = sessionId,
|
customTabSessionId = sessionId,
|
||||||
webAppManifestUrl = url,
|
webAppManifestUrl = url,
|
||||||
|
pwaProfileUuid = profileUuid,
|
||||||
|
pwaContextId = contextId,
|
||||||
|
pwaToken = token,
|
||||||
|
pwaInstallStartUrl = installStartUrl ?: url,
|
||||||
)
|
)
|
||||||
startActivity(externalIntent)
|
startActivity(externalIntent)
|
||||||
finish()
|
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.
|
* Creates a custom tab session for a PWA with the specified context ID.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user