improve intent handling

This commit is contained in:
Fabian Freund
2026-04-28 12:46:11 +02:00
parent 24741720ed
commit 8fe34158ff
5 changed files with 116 additions and 17 deletions
@@ -108,17 +108,7 @@
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</activity>
<!-- Intent router: receives external VIEW/SEND intents and routes
Custom Tab / PWA to ExternalAppBrowserActivity, regular intents to MainActivity -->
@@ -170,6 +160,17 @@
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.PROCESS_TEXT" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.WEB_SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
<!-- PWA launch from home screen shortcut -->
<intent-filter>
<action android:name="mozilla.components.feature.pwa.VIEW_PWA" />
@@ -344,7 +344,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
val appLinksUseCases = components.useCases.appLinksUseCases
val getRedirect = appLinksUseCases.appLinkRedirect
val redirect = getRedirect.invoke(fallbackUrl)
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
redirect.appIntent?.flags =
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
}
},
@@ -295,6 +295,9 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
type = "text/plain"
putExtra(Intent.EXTRA_TEXT, tab.content.url)
putExtra(Intent.EXTRA_SUBJECT, tab.content.title)
// Use NEW_DOCUMENT + MULTIPLE_TASK so the receiving app opens
// in its own task rather than inside WebLibre's recents entry.
flags = Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
}
startActivity(Intent.createChooser(shareIntent, null))
}
@@ -33,11 +33,12 @@ import mozilla.components.browser.state.state.ExternalAppType
import java.io.File
/**
* Lightweight transparent activity that receives all ACTION_VIEW intents and routes them
* Lightweight transparent activity that receives all external intents and routes them
* to the appropriate activity:
* - Custom Tab intents → [ExternalAppBrowserActivity]
* - PWA launch intents → [ExternalAppBrowserActivity] (with profile/context tracking)
* - Regular VIEW intents → MainActivity (Flutter)
* - SHARE intents with a URL → [ExternalAppBrowserActivity] (separate recents entry)
* - Regular VIEW / SEND / PROCESS_TEXT / WEB_SEARCH intents → MainActivity (Flutter)
*
* For PWA intents created by our custom installer, checks profile match and shows dialog
* if the current profile differs from the installation profile.
@@ -135,6 +136,23 @@ class IntentReceiverActivity : Activity() {
val privateBrowsingMode = intent.getBooleanExtra(PRIVATE_BROWSING_MODE, false)
intent.putExtra(PRIVATE_BROWSING_MODE, privateBrowsingMode)
// SHARE intents with a URL are routed to ExternalAppBrowserActivity so they
// appear as a separate recents entry ("share as new task"), matching the
// behaviour users expect when sharing content into the browser. SEND intents
// without a URL (plain text, files) fall through to MainActivity for the
// Flutter-side to handle (search, PDF display, etc.).
if (intent.action == Intent.ACTION_SEND) {
val shareUrl = extractShareUrl(intent)
if (shareUrl != null) {
Log.d(TAG, "SHARE intent with URL, routing to custom tab: $shareUrl")
handleShareUrlAsCustomTab(intent, shareUrl, privateBrowsingMode)
return
}
Log.d(TAG, "SHARE intent without URL, routing to MainActivity")
handleRegularIntent(intent)
return
}
// Check if this is our custom PWA intent with profile metadata
val profileUuid = intent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID)
val contextId = intent.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID)
@@ -493,6 +511,80 @@ class IntentReceiverActivity : Activity() {
return tab.id
}
/**
* Extracts a URL from a SEND intent.
* First checks EXTRA_TEXT for a URL, then EXTRA_STREAM.
* Returns null if no valid URL is found.
*/
private fun extractShareUrl(intent: Intent): String? {
fun String?.toHttpUrl(): String? {
val candidate = this?.trim().orEmpty()
return candidate.takeIf {
it.startsWith("http://") || it.startsWith("https://")
}
}
intent.getStringExtra(Intent.EXTRA_TEXT)?.toHttpUrl()?.let { return it }
@Suppress("DEPRECATION")
val streamUri: Uri? = intent.getParcelableExtra(Intent.EXTRA_STREAM)
streamUri?.toString().toHttpUrl()?.let { return it }
intent.dataString.toHttpUrl()?.let { return it }
return null
}
/**
* Creates a custom tab session for the shared URL and launches
* [ExternalAppBrowserActivity], which appears as a separate recents entry
* because it uses taskAffinity="" and documentLaunchMode="always".
*/
private fun handleShareUrlAsCustomTab(
sourceIntent: Intent,
url: String,
privateBrowsingMode: Boolean,
) {
var components = GlobalComponents.components
if (components == null) {
if (!GlobalComponents.ensureExternalComponents(applicationContext)) {
Log.w(TAG, "Components not initialized, falling back to MainActivity for SHARE")
handleRegularIntent(sourceIntent)
return
}
components = GlobalComponents.components
}
if (components == null) {
Log.e(TAG, "Components still null after init, falling back to MainActivity for SHARE")
handleRegularIntent(sourceIntent)
return
}
val customTabConfig = mozilla.components.browser.state.state.CustomTabConfig()
val tab = mozilla.components.browser.state.state.createCustomTab(
url = url,
config = customTabConfig,
source = mozilla.components.browser.state.state.SessionState.Source.Internal.CustomTab,
private = privateBrowsingMode,
)
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)
val externalIntent = ExternalAppBrowserActivity.createIntent(
context = this,
customTabSessionId = tab.id,
)
startActivity(externalIntent)
finish()
}
private fun handleRegularIntent(intent: Intent) {
val mainActivityIntent = Intent(intent).apply {
setClassName(this@IntentReceiverActivity, "eu.weblibre.gecko.MainActivity")
@@ -54,9 +54,11 @@ class GeckoAppLinksApiImpl(
return@launch
}
// Set FLAG_ACTIVITY_NEW_TASK to launch in new task
// This prevents issues with app task stacks
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
// Use NEW_DOCUMENT + MULTIPLE_TASK so the target app opens in its own
// task and doesn't get absorbed into WebLibre's recents entry.
// This matches Fenix's ShareController behaviour.
redirect.appIntent?.flags =
Intent.FLAG_ACTIVITY_NEW_DOCUMENT or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
components.useCases.appLinksUseCases.openAppLink.invoke(redirect.appIntent)
callback(Result.success(true))