diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6284d6d..d5640f9 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).
+## [2.0.3]
+
+### Fixes
+
+- Keep the main app process active while the WebView is in the foreground, by binding it to the WebView's process. Without this, Android can freeze the main process during a browser session, so browser events stop being processed and the app never reacts to the page finishing ([RMET-5394](https://outsystemsrd.atlassian.net/browse/RMET-5394)).
+
## [2.0.2]
### Fixes
diff --git a/pom.xml b/pom.xml
index 259c045..41e4cf8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,5 +6,5 @@
4.0.0
io.ionic.libs
ioninappbrowser-android
- 2.0.2
+ 2.0.3
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 2218bcf..10c7dc4 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -49,6 +49,9 @@
android:resource="@xml/file_paths" />
+
diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABKeepAliveService.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABKeepAliveService.kt
new file mode 100644
index 0000000..bbbcced
--- /dev/null
+++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABKeepAliveService.kt
@@ -0,0 +1,15 @@
+package com.outsystems.plugins.inappbrowser.osinappbrowserlib
+
+import android.app.Service
+import android.content.Intent
+import android.os.Binder
+import android.os.IBinder
+
+/**
+ * Runs in the main app process. While the isolated WebView activity is bound to it,
+ * the main process is not eligible for OS app freezing, so it keeps processing
+ * browser events while the WebView is in the foreground.
+ */
+class OSIABKeepAliveService : Service() {
+ override fun onBind(intent: Intent?): IBinder = Binder()
+}
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 6665832..b8a5d5a 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
@@ -4,14 +4,17 @@ import android.Manifest
import android.app.Application
import android.app.Activity
import android.content.BroadcastReceiver
+import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
+import android.content.ServiceConnection
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Bundle
+import android.os.IBinder
import android.provider.MediaStore
import android.util.Log
import android.view.Gravity
@@ -42,6 +45,7 @@ import androidx.core.content.FileProvider
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents
+import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABKeepAliveService
import com.outsystems.plugins.inappbrowser.osinappbrowserlib.R
import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABPdfHelper
import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABToolbarPosition
@@ -73,6 +77,8 @@ open class OSIABWebViewActivity : AppCompatActivity() {
private var closeReceiver: BroadcastReceiver? = null
+ private var keepAliveConnection: ServiceConnection? = null
+
// for the browserPageLoaded event, which we only want to trigger on the first URL loaded in the WebView
private var isFirstLoad = true
@@ -139,6 +145,11 @@ open class OSIABWebViewActivity : AppCompatActivity() {
const val REQUEST_LOCATION_PERMISSION = 623
const val REQUEST_CAMERA_PERMISSION = 624
const val LOG_TAG = "OSIABWebViewActivity"
+ const val ISOLATED_PROCESS_SUFFIX = ":OSInAppBrowser"
+
+ private fun isIsolatedProcess(): Boolean =
+ Build.VERSION.SDK_INT >= Build.VERSION_CODES.P &&
+ Application.getProcessName().endsWith(ISOLATED_PROCESS_SUFFIX)
val errorsToHandle = listOf(
WebViewClient.ERROR_HOST_LOOKUP,
WebViewClient.ERROR_UNSUPPORTED_SCHEME,
@@ -153,15 +164,12 @@ open class OSIABWebViewActivity : AppCompatActivity() {
}
init {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- try {
- val processName = Application.getProcessName()
- if (processName.endsWith(":OSInAppBrowser")) {
- WebView.setDataDirectorySuffix("OSInAppBrowser")
- }
- } catch (e: Exception) {
- Log.d(LOG_TAG, "Suffix already set or error: ${e.message}")
+ try {
+ if (isIsolatedProcess()) {
+ WebView.setDataDirectorySuffix("OSInAppBrowser")
}
+ } catch (e: Exception) {
+ Log.d(LOG_TAG, "Suffix already set or error: ${e.message}")
}
}
}
@@ -180,6 +188,17 @@ open class OSIABWebViewActivity : AppCompatActivity() {
browserId = intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID) ?: ""
+ // keep the main process out of the freezable state while the browser is in front,
+ // otherwise events queue and the app's close flow stalls until it unfreezes
+ if (isIsolatedProcess()) {
+ val connection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName?, service: IBinder?) {}
+ override fun onServiceDisconnected(name: ComponentName?) {}
+ }
+ bindService(Intent(this, OSIABKeepAliveService::class.java), connection, Context.BIND_AUTO_CREATE)
+ keepAliveConnection = connection
+ }
+
// Register receiver for close commands from main process
closeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
@@ -280,6 +299,14 @@ open class OSIABWebViewActivity : AppCompatActivity() {
}
closeReceiver = null
}
+ keepAliveConnection?.let {
+ try {
+ unbindService(it)
+ } catch (e: Exception) {
+ // Service may not be bound, ignore
+ }
+ keepAliveConnection = null
+ }
webView.destroy()
super.onDestroy()
}