improve intent handling
This commit is contained in:
@@ -108,17 +108,7 @@
|
|||||||
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
<action android:name="es.antonborri.home_widget.action.LAUNCH" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
<intent-filter>
|
</activity>
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Intent router: receives external VIEW/SEND intents and routes
|
<!-- Intent router: receives external VIEW/SEND intents and routes
|
||||||
Custom Tab / PWA to ExternalAppBrowserActivity, regular intents to MainActivity -->
|
Custom Tab / PWA to ExternalAppBrowserActivity, regular intents to MainActivity -->
|
||||||
@@ -170,6 +160,17 @@
|
|||||||
<data android:mimeType="text/*" />
|
<data android:mimeType="text/*" />
|
||||||
</intent-filter>
|
</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 -->
|
<!-- PWA launch from home screen shortcut -->
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="mozilla.components.feature.pwa.VIEW_PWA" />
|
<action android:name="mozilla.components.feature.pwa.VIEW_PWA" />
|
||||||
|
|||||||
+2
-1
@@ -344,7 +344,8 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler, Activit
|
|||||||
val appLinksUseCases = components.useCases.appLinksUseCases
|
val appLinksUseCases = components.useCases.appLinksUseCases
|
||||||
val getRedirect = appLinksUseCases.appLinkRedirect
|
val getRedirect = appLinksUseCases.appLinkRedirect
|
||||||
val redirect = getRedirect.invoke(fallbackUrl)
|
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)
|
appLinksUseCases.openAppLink.invoke(redirect.appIntent)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
+3
@@ -295,6 +295,9 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
|
|||||||
type = "text/plain"
|
type = "text/plain"
|
||||||
putExtra(Intent.EXTRA_TEXT, tab.content.url)
|
putExtra(Intent.EXTRA_TEXT, tab.content.url)
|
||||||
putExtra(Intent.EXTRA_SUBJECT, tab.content.title)
|
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))
|
startActivity(Intent.createChooser(shareIntent, null))
|
||||||
}
|
}
|
||||||
|
|||||||
+94
-2
@@ -33,11 +33,12 @@ import mozilla.components.browser.state.state.ExternalAppType
|
|||||||
import java.io.File
|
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:
|
* to the appropriate activity:
|
||||||
* - Custom Tab intents → [ExternalAppBrowserActivity]
|
* - Custom Tab intents → [ExternalAppBrowserActivity]
|
||||||
* - PWA launch intents → [ExternalAppBrowserActivity] (with profile/context tracking)
|
* - 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
|
* For PWA intents created by our custom installer, checks profile match and shows dialog
|
||||||
* if the current profile differs from the installation profile.
|
* if the current profile differs from the installation profile.
|
||||||
@@ -135,6 +136,23 @@ class IntentReceiverActivity : Activity() {
|
|||||||
val privateBrowsingMode = intent.getBooleanExtra(PRIVATE_BROWSING_MODE, false)
|
val privateBrowsingMode = intent.getBooleanExtra(PRIVATE_BROWSING_MODE, false)
|
||||||
intent.putExtra(PRIVATE_BROWSING_MODE, privateBrowsingMode)
|
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
|
// Check if this is our custom PWA intent with profile metadata
|
||||||
val profileUuid = intent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID)
|
val profileUuid = intent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID)
|
||||||
val contextId = intent.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID)
|
val contextId = intent.getStringExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID)
|
||||||
@@ -493,6 +511,80 @@ class IntentReceiverActivity : Activity() {
|
|||||||
return tab.id
|
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) {
|
private fun handleRegularIntent(intent: Intent) {
|
||||||
val mainActivityIntent = Intent(intent).apply {
|
val mainActivityIntent = Intent(intent).apply {
|
||||||
setClassName(this@IntentReceiverActivity, "eu.weblibre.gecko.MainActivity")
|
setClassName(this@IntentReceiverActivity, "eu.weblibre.gecko.MainActivity")
|
||||||
|
|||||||
+5
-3
@@ -54,9 +54,11 @@ class GeckoAppLinksApiImpl(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set FLAG_ACTIVITY_NEW_TASK to launch in new task
|
// Use NEW_DOCUMENT + MULTIPLE_TASK so the target app opens in its own
|
||||||
// This prevents issues with app task stacks
|
// task and doesn't get absorbed into WebLibre's recents entry.
|
||||||
redirect.appIntent?.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
// 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)
|
components.useCases.appLinksUseCases.openAppLink.invoke(redirect.appIntent)
|
||||||
callback(Result.success(true))
|
callback(Result.success(true))
|
||||||
|
|||||||
Reference in New Issue
Block a user