From f9a3b3c6d0e6554d84b27e22077452e1246aedb6 Mon Sep 17 00:00:00 2001 From: Chace Daniels Date: Mon, 19 Jan 2026 14:55:08 -0600 Subject: [PATCH 1/2] fix: separate local storage between WebView processes --- src/main/AndroidManifest.xml | 1 + .../osinappbrowserlib/OSIABEvents.kt | 61 ++++++++++++++++++- .../helpers/OSIABCustomTabsSessionHelper.kt | 3 + .../helpers/OSIABFlowHelper.kt | 5 ++ .../helpers/OSIABFlowHelperInterface.kt | 5 ++ .../OSIABCustomTabsRouterAdapter.kt | 5 ++ .../OSIABWebViewRouterAdapter.kt | 3 + .../views/OSIABWebViewActivity.kt | 12 +++- 8 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 4def0b9..bf36993 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ () + private val _events = MutableSharedFlow(extraBufferCapacity = 64) val events = _events.asSharedFlow() + private var receiver: BroadcastReceiver? = null + + @SuppressLint("UnspecifiedRegisterReceiverFlag") + fun registerReceiver(context: Context) { + if (receiver != null) return + + val appContext = context.applicationContext + receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + if (intent?.action == ACTION_IAB_EVENT) { + @Suppress("DEPRECATION") + val event = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.getSerializableExtra(EXTRA_EVENT_DATA, OSIABEvents::class.java) + } else { + intent.getSerializableExtra(EXTRA_EVENT_DATA) as? OSIABEvents + } + event?.let { + _events.tryEmit(it) + } + } + } + } + + val filter = IntentFilter(ACTION_IAB_EVENT) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + appContext.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED) + } else { + appContext.registerReceiver(receiver, filter) + } + } + suspend fun postEvent(event: OSIABEvents) { _events.emit(event) } + + fun broadcastEvent(context: Context, event: OSIABEvents) { + val intent = Intent(ACTION_IAB_EVENT).apply { + setPackage(context.packageName) + putExtra(EXTRA_EVENT_DATA, event) + } + context.sendBroadcast(intent) + } } } diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABCustomTabsSessionHelper.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABCustomTabsSessionHelper.kt index a26ca53..a5e5862 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABCustomTabsSessionHelper.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABCustomTabsSessionHelper.kt @@ -11,6 +11,7 @@ import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection import androidx.browser.customtabs.CustomTabsSession import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.RequiresEventBridgeRegistration import com.outsystems.plugins.inappbrowser.osinappbrowserlib.views.OSIABCustomTabsControllerActivity import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -35,6 +36,7 @@ class OSIABCustomTabsSessionHelper: OSIABCustomTabsSessionHelperInterface { flowHelper: OSIABFlowHelperInterface, customTabsSessionCallback: (CustomTabsSession?) -> Unit ) { + OSIABEvents.registerReceiver(context) CustomTabsClient.bindCustomTabsService( context, packageName, @@ -54,6 +56,7 @@ class OSIABCustomTabsSessionHelper: OSIABCustomTabsSessionHelperInterface { ) } + @OptIn(RequiresEventBridgeRegistration::class) private inner class CustomTabsCallbackImpl( private val browserId: String, private val lifecycleScope: CoroutineScope, diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelper.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelper.kt index 3ae711b..f312132 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelper.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelper.kt @@ -1,6 +1,7 @@ package com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.RequiresEventBridgeRegistration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.flow.transformWhile @@ -14,7 +15,11 @@ class OSIABFlowHelper: OSIABFlowHelperInterface { * @param browserId Identifier for the browser instance to emit events to * @param scope CoroutineScope to launch * @param onEventReceived callback to send the collected event in + * + * @note For Android API 28+, you must call [OSIABEvents.registerReceiver] once during your application + * or activity lifecycle to ensure events from the isolated browser process are correctly received and bridged. */ + @RequiresEventBridgeRegistration override fun listenToEvents( browserId: String, scope: CoroutineScope, diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelperInterface.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelperInterface.kt index 7c0c329..0ff2bbc 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelperInterface.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/helpers/OSIABFlowHelperInterface.kt @@ -1,6 +1,7 @@ package com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.RequiresEventBridgeRegistration import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @@ -12,7 +13,11 @@ interface OSIABFlowHelperInterface { * @param browserId Identifier for the browser instance to emit events to * @param scope CoroutineScope to launch * @param onEventReceived callback to send the collected event in + * + * @note For Android API 28+, you must call [OSIABEvents.registerReceiver] once during your application + * or activity lifecycle to ensure events from the isolated browser process are correctly received and bridged. */ + @RequiresEventBridgeRegistration fun listenToEvents( browserId: String, scope: CoroutineScope, diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABCustomTabsRouterAdapter.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABCustomTabsRouterAdapter.kt index 0c4e69a..08df9d1 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABCustomTabsRouterAdapter.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABCustomTabsRouterAdapter.kt @@ -6,6 +6,7 @@ import android.net.Uri import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsSession import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.RequiresEventBridgeRegistration import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABCustomTabsSessionHelper import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABCustomTabsSessionHelperInterface import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABFlowHelperInterface @@ -40,7 +41,9 @@ class OSIABCustomTabsRouterAdapter( // for the browserPageLoaded event, which we only want to trigger on the first URL loaded in the CustomTabs instance private var isFirstLoad = true + @OptIn(RequiresEventBridgeRegistration::class) override fun close(completionHandler: (Boolean) -> Unit) { + OSIABEvents.registerReceiver(context) var closeEventJob: Job? = null closeEventJob = flowHelper.listenToEvents(browserId, lifecycleScope) { event -> @@ -141,6 +144,7 @@ class OSIABCustomTabsRouterAdapter( } override fun handleOpen(url: String, completionHandler: (Boolean) -> Unit) { + OSIABEvents.registerReceiver(context) lifecycleScope.launch { try { val uri = Uri.parse(url) @@ -165,6 +169,7 @@ class OSIABCustomTabsRouterAdapter( } } + @OptIn(RequiresEventBridgeRegistration::class) private fun openCustomTabsIntent(session: CustomTabsSession, uri: Uri, completionHandler: (Boolean) -> Unit) { val customTabsIntent = buildCustomTabsIntent(session) var eventsJob: Job? = null diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABWebViewRouterAdapter.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABWebViewRouterAdapter.kt index 6f5b741..951ffc5 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABWebViewRouterAdapter.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/routeradapters/OSIABWebViewRouterAdapter.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.os.Bundle import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.RequiresEventBridgeRegistration import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABFlowHelperInterface import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABWebViewOptions import com.outsystems.plugins.inappbrowser.osinappbrowserlib.views.OSIABWebViewActivity @@ -71,10 +72,12 @@ class OSIABWebViewRouterAdapter( * @param url URL to be opened. * @param completionHandler The callback with the result of opening the url. */ + @OptIn(RequiresEventBridgeRegistration::class) override fun handleOpen(url: String, completionHandler: (Boolean) -> Unit) { lifecycleScope.launch { try { // Collect the browser events + OSIABEvents.registerReceiver(context) var eventsJob: Job? = null eventsJob = flowHelper.listenToEvents(browserId, lifecycleScope) { event -> when (event) { diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt index 59ec090..0b42254 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABWebViewActivity.kt @@ -148,6 +148,15 @@ class OSIABWebViewActivity : AppCompatActivity() { return File.createTempFile("${prefix}${timeStamp}_", suffix, storageDir) } + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + try { + WebView.setDataDirectorySuffix("OSInAppBrowser") + } catch (e: Exception) { + Log.d(LOG_TAG, "Suffix already set or error: ${e.message}") + } + } + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -164,7 +173,7 @@ class OSIABWebViewActivity : AppCompatActivity() { browserId = intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID) ?: "" - sendWebViewEvent(OSIABWebViewEvent(browserId, this@OSIABWebViewActivity)) + sendWebViewEvent(OSIABEvents.OSIABWebViewEvent(browserId)) appName = applicationInfo.loadLabel(packageManager).toString() @@ -917,6 +926,7 @@ class OSIABWebViewActivity : AppCompatActivity() { private fun sendWebViewEvent(event: OSIABEvents) { lifecycleScope.launch { OSIABEvents.postEvent(event) + OSIABEvents.broadcastEvent(this@OSIABWebViewActivity, event) } } From 60c07587e1dca8b0f3836fea8a34ca13e43b48ae Mon Sep 17 00:00:00 2001 From: Chace Daniels Date: Mon, 19 Jan 2026 15:01:20 -0600 Subject: [PATCH 2/2] chore: update pom.xml version and changelog --- CHANGELOG.md | 6 ++++++ pom.xml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fcac34..61b6e47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.7.0] + +### Fixes + +- Fix issue with local storage isolation between WebView and main app on Android 28+ [RMET-4918](https://outsystemsrd.atlassian.net/browse/RMET-4918) + ## [1.6.1] ### Fixes diff --git a/pom.xml b/pom.xml index 3ab5e59..c704fb1 100644 --- a/pom.xml +++ b/pom.xml @@ -6,5 +6,5 @@ 4.0.0 io.ionic.libs ioninappbrowser-android - 1.6.1 + 1.7.0