From 5ac57024fce6174d0bf77a46eb492481818cdbd5 Mon Sep 17 00:00:00 2001 From: Rui Mendes Date: Tue, 7 Jul 2026 15:29:24 +0100 Subject: [PATCH 1/3] fix: only fire browserClosed when the custom tab actually terminates --- .../helpers/OSIABCustomTabsSessionHelper.kt | 58 +++---- .../OSIABCustomTabsRouterAdapter.kt | 147 ++++++++---------- .../OSIABCustomTabsControllerActivity.kt | 59 ++++--- 3 files changed, 122 insertions(+), 142 deletions(-) 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 f45ac20..df80cac 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 @@ -8,6 +8,7 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle +import android.util.Log import androidx.browser.customtabs.CustomTabsCallback import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection @@ -18,6 +19,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch +private const val TAG = "OSIABSession" + class OSIABCustomTabsSessionHelper: OSIABCustomTabsSessionHelperInterface { private fun getDefaultCustomTabsPackageName(context: Context): String? { val activityIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://")) @@ -62,56 +65,35 @@ class OSIABCustomTabsSessionHelper: OSIABCustomTabsSessionHelperInterface { flowHelper: OSIABFlowHelperInterface, ) : CustomTabsCallback() { - private var isCustomTabsActivityOnTop = false - private var pendingTabHiddenEvent = false - init { var browserEventsJob: Job? = null - browserEventsJob = flowHelper.listenToEvents(browserId, lifecycleScope) { event -> - if(event is OSIABEvents.OSIABCustomTabsEvent) { - when (event.action) { - OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_RESUMED -> { - isCustomTabsActivityOnTop = true - if (pendingTabHiddenEvent) { - pendingTabHiddenEvent = false - lifecycleScope.launch { - OSIABEvents.postEvent(OSIABEvents.BrowserFinished(browserId)) - } - } - } - OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_PAUSED -> { - isCustomTabsActivityOnTop = false - pendingTabHiddenEvent = false - } - OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED -> { - browserEventsJob?.cancel() - } - } + if (event is OSIABEvents.OSIABCustomTabsEvent + && event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED) { + browserEventsJob?.cancel() } } } override fun onNavigationEvent(navigationEvent: Int, extras: Bundle?) { super.onNavigationEvent(navigationEvent, extras) - val browserEvent = when (navigationEvent) { - NAVIGATION_FINISHED -> OSIABEvents.BrowserPageLoaded(browserId) - TAB_HIDDEN -> { - if(isCustomTabsActivityOnTop) { - OSIABEvents.BrowserFinished(browserId) - } - else { - // App not open but custom tabs is hidden (home button, recent apps, etc.) - pendingTabHiddenEvent = true - return - } + Log.d(TAG, "onNavigationEvent: code=$navigationEvent (${navEventName(navigationEvent)})") + if (navigationEvent == NAVIGATION_FINISHED) { + lifecycleScope.launch { + OSIABEvents.postEvent(OSIABEvents.BrowserPageLoaded(browserId)) } - else -> return - } - lifecycleScope.launch { - OSIABEvents.postEvent(browserEvent) } } + + private fun navEventName(code: Int): String = when (code) { + NAVIGATION_STARTED -> "NAVIGATION_STARTED" + NAVIGATION_FINISHED -> "NAVIGATION_FINISHED" + NAVIGATION_FAILED -> "NAVIGATION_FAILED" + NAVIGATION_ABORTED -> "NAVIGATION_ABORTED" + TAB_SHOWN -> "TAB_SHOWN" + TAB_HIDDEN -> "TAB_HIDDEN" + else -> "UNKNOWN" + } } override suspend fun generateNewCustomTabsSession( 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 d6d1cb5..a0b518e 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 @@ -5,6 +5,7 @@ package com.outsystems.plugins.inappbrowser.osinappbrowserlib.routeradapters import android.content.Context import android.content.Intent import android.net.Uri +import android.util.Log import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsSession import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents @@ -39,11 +40,16 @@ class OSIABCustomTabsRouterAdapter( private val browserId = UUID.randomUUID().toString() + private companion object { + const val TAG = "OSIABRouter" + } + // for the browserPageLoaded event, which we only want to trigger on the first URL loaded in the CustomTabs instance private var isFirstLoad = true private var isFinished = false override fun close(completionHandler: (Boolean) -> Unit) { + Log.d(TAG, "close called (isFinished=$isFinished, browserId=$browserId)") if (isFinished) { completionHandler(true) return @@ -51,23 +57,29 @@ class OSIABCustomTabsRouterAdapter( var closeEventJob: Job? = null closeEventJob = flowHelper.listenToEvents(browserId, lifecycleScope) { event -> - if(event is OSIABEvents.OSIABCustomTabsEvent) { - when(event.action) { - OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_READY -> { - completionHandler(false) - } - OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED -> { - completionHandler(true) - } - else -> { - return@listenToEvents - } - } + if(event is OSIABEvents.OSIABCustomTabsEvent + && event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED) { + Log.d(TAG, "close: DESTROYED received -> completionHandler(true)") + completionHandler(true) closeEventJob?.cancel() } } - startCustomTabsControllerActivity(true) + startCustomTabsControllerActivity(doClose = true) + } + + private fun resolveStartAnimationRes(animation: OSIABAnimation): Pair = when (animation) { + OSIABAnimation.FADE_IN -> android.R.anim.fade_in to android.R.anim.fade_out + OSIABAnimation.FADE_OUT -> android.R.anim.fade_out to android.R.anim.fade_in + OSIABAnimation.SLIDE_IN_LEFT -> android.R.anim.slide_in_left to android.R.anim.slide_out_right + OSIABAnimation.SLIDE_OUT_RIGHT -> android.R.anim.slide_out_right to android.R.anim.slide_in_left + } + + private fun resolveExitAnimationRes(animation: OSIABAnimation): Pair = when (animation) { + OSIABAnimation.FADE_IN -> android.R.anim.fade_out to android.R.anim.fade_in + OSIABAnimation.FADE_OUT -> android.R.anim.fade_in to android.R.anim.fade_out + OSIABAnimation.SLIDE_IN_LEFT -> android.R.anim.slide_out_right to android.R.anim.slide_in_left + OSIABAnimation.SLIDE_OUT_RIGHT -> android.R.anim.slide_in_left to android.R.anim.slide_out_right } private fun buildCustomTabsIntent(customTabsSession: CustomTabsSession?): CustomTabsIntent { @@ -76,57 +88,11 @@ class OSIABCustomTabsRouterAdapter( builder.setShowTitle(options.showTitle) builder.setUrlBarHidingEnabled(options.hideToolbarOnScroll) - when (options.startAnimation) { - OSIABAnimation.FADE_IN -> builder.setStartAnimations( - context, - android.R.anim.fade_in, - android.R.anim.fade_out - ) - - OSIABAnimation.FADE_OUT -> builder.setStartAnimations( - context, - android.R.anim.fade_out, - android.R.anim.fade_in - ) - - OSIABAnimation.SLIDE_IN_LEFT -> builder.setStartAnimations( - context, - android.R.anim.slide_in_left, - android.R.anim.slide_out_right - ) - - OSIABAnimation.SLIDE_OUT_RIGHT -> builder.setStartAnimations( - context, - android.R.anim.slide_out_right, - android.R.anim.slide_in_left - ) - } - - when (options.exitAnimation) { - OSIABAnimation.FADE_IN -> builder.setExitAnimations( - context, - android.R.anim.fade_out, - android.R.anim.fade_in - ) - - OSIABAnimation.FADE_OUT -> builder.setExitAnimations( - context, - android.R.anim.fade_in, - android.R.anim.fade_out - ) - - OSIABAnimation.SLIDE_IN_LEFT -> builder.setExitAnimations( - context, - android.R.anim.slide_out_right, - android.R.anim.slide_in_left - ) + val (startEnter, startExit) = resolveStartAnimationRes(options.startAnimation) + builder.setStartAnimations(context, startEnter, startExit) - OSIABAnimation.SLIDE_OUT_RIGHT -> builder.setExitAnimations( - context, - android.R.anim.slide_in_left, - android.R.anim.slide_out_right - ) - } + val (exitEnter, exitExit) = resolveExitAnimationRes(options.exitAnimation) + builder.setExitAnimations(context, exitEnter, exitExit) if (options.viewStyle == OSIABViewStyle.BOTTOM_SHEET) { options.bottomSheetOptions?.let { bottomSheetOptions -> @@ -148,6 +114,7 @@ class OSIABCustomTabsRouterAdapter( } override fun handleOpen(url: String, completionHandler: (Boolean) -> Unit) { + Log.d(TAG, "handleOpen: url=$url, browserId=$browserId") lifecycleScope.launch { try { val uri = Uri.parse(url) @@ -158,37 +125,31 @@ class OSIABCustomTabsRouterAdapter( flowHelper, customTabsSessionCallback = { if(it == null) { + Log.d(TAG, "handleOpen: session is null -> completionHandler(false)") completionHandler(false) return@generateNewCustomTabsSession } - openCustomTabsIntent(it, uri, completionHandler) - startCustomTabsControllerActivity() } ) } catch (e: Exception) { + Log.d(TAG, "handleOpen: exception ${e.message}") completionHandler(false) } } } private fun openCustomTabsIntent(session: CustomTabsSession, uri: Uri, completionHandler: (Boolean) -> Unit) { + Log.d(TAG, "openCustomTabsIntent: uri=$uri") val customTabsIntent = buildCustomTabsIntent(session) + customTabsIntent.intent.data = uri + var eventsJob: Job? = null eventsJob = flowHelper.listenToEvents(browserId, lifecycleScope) { event -> when (event) { is OSIABEvents.OSIABCustomTabsEvent -> { - if(event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_READY) { - try { - event.context?.let { ctx -> - customTabsIntent.launchUrl(ctx, uri) - completionHandler(true) - } ?: completionHandler(false) - } catch (e: Exception) { - completionHandler(false) - } - } - else if(event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED) { + if(event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED) { + Log.d(TAG, "openCustomTabsIntent: DESTROYED -> onBrowserFinished()") isFinished = true onBrowserFinished() eventsJob?.cancel() @@ -196,23 +157,35 @@ class OSIABCustomTabsRouterAdapter( } is OSIABEvents.BrowserPageLoaded -> { if (isFirstLoad) { + Log.d(TAG, "openCustomTabsIntent: BrowserPageLoaded -> onBrowserPageLoaded()") onBrowserPageLoaded() isFirstLoad = false } } - is OSIABEvents.BrowserFinished -> { - // Ensure that custom tabs controller activity is fully destroyed - startCustomTabsControllerActivity(true) - isFinished = true - onBrowserFinished() - eventsJob?.cancel() - } else -> {} } } + + try { + val (startEnter, startExit) = resolveStartAnimationRes(options.startAnimation) + startCustomTabsControllerActivity( + customTabsIntent = customTabsIntent.intent, + startEnterAnimRes = startEnter, + startExitAnimRes = startExit + ) + completionHandler(true) + } catch (e: Exception) { + eventsJob?.cancel() + completionHandler(false) + } } - private fun startCustomTabsControllerActivity(doClose: Boolean = false) { + private fun startCustomTabsControllerActivity( + doClose: Boolean = false, + customTabsIntent: Intent? = null, + startEnterAnimRes: Int = 0, + startExitAnimRes: Int = 0 + ) { val intent = Intent(context, OSIABCustomTabsControllerActivity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP) putExtra(OSIABEvents.EXTRA_BROWSER_ID, browserId) @@ -223,6 +196,12 @@ class OSIABCustomTabsRouterAdapter( intent.putExtra(OSIABCustomTabsControllerActivity.ACTION_CLOSE_CUSTOM_TABS, true) } + if (customTabsIntent != null) { + intent.putExtra(OSIABCustomTabsControllerActivity.EXTRA_CUSTOM_TABS_INTENT, customTabsIntent) + intent.putExtra(OSIABCustomTabsControllerActivity.EXTRA_START_ENTER_ANIM_RES, startEnterAnimRes) + intent.putExtra(OSIABCustomTabsControllerActivity.EXTRA_START_EXIT_ANIM_RES, startExitAnimRes) + } + context.startActivity(intent) } } diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt index 3532ecf..0000dba 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt @@ -2,8 +2,13 @@ package com.outsystems.plugins.inappbrowser.osinappbrowserlib.views import android.content.Intent import android.os.Bundle +import android.util.Log import android.view.WindowManager +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityOptionsCompat +import androidx.core.content.IntentCompat import androidx.lifecycle.lifecycleScope import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents.OSIABCustomTabsEvent @@ -17,54 +22,68 @@ import kotlinx.coroutines.launch class OSIABCustomTabsControllerActivity: AppCompatActivity() { companion object { + private const val TAG = "OSIABCT" const val EVENT_CUSTOM_TABS_DESTROYED = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EVENT_CUSTOM_TABS_DESTROYED" - const val EVENT_CUSTOM_TABS_READY = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EVENT_CUSTOM_TABS_READY" - const val EVENT_CUSTOM_TABS_PAUSED = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EVENT_CUSTOM_TABS_PAUSED" - const val EVENT_CUSTOM_TABS_RESUMED = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EVENT_CUSTOM_TABS_RESUMED" const val ACTION_CLOSE_CUSTOM_TABS = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.ACTION_CLOSE_CUSTOM_TABS" + const val EXTRA_CUSTOM_TABS_INTENT = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EXTRA_CUSTOM_TABS_INTENT" + const val EXTRA_START_ENTER_ANIM_RES = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EXTRA_START_ENTER_ANIM_RES" + const val EXTRA_START_EXIT_ANIM_RES = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EXTRA_START_EXIT_ANIM_RES" } + private var customTabsLauncher: ActivityResultLauncher? = null + private var hasLaunchedCustomTabs = false + + private fun setup(intent: Intent) { + Log.d(TAG, "setup: hasLaunchedCustomTabs=$hasLaunchedCustomTabs, doClose=${intent.getBooleanExtra(ACTION_CLOSE_CUSTOM_TABS, false)}") window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) if (intent.getBooleanExtra(ACTION_CLOSE_CUSTOM_TABS, false)) { + Log.d(TAG, "setup: close action -> finish()") finish() + return } - else { - intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID)?.let { browserId -> - sendCustomTabsEvent(lifecycleScope, browserId, EVENT_CUSTOM_TABS_READY) + + if (!hasLaunchedCustomTabs) { + val customTabsIntent = IntentCompat.getParcelableExtra(intent, EXTRA_CUSTOM_TABS_INTENT, Intent::class.java) + if (customTabsIntent != null) { + hasLaunchedCustomTabs = true + val enterAnimRes = intent.getIntExtra(EXTRA_START_ENTER_ANIM_RES, 0) + val exitAnimRes = intent.getIntExtra(EXTRA_START_EXIT_ANIM_RES, 0) + val options = if (enterAnimRes != 0 && exitAnimRes != 0) { + ActivityOptionsCompat.makeCustomAnimation(this, enterAnimRes, exitAnimRes) + } else { + null + } + Log.d(TAG, "setup: launching CCT (enter=$enterAnimRes, exit=$exitAnimRes)") + customTabsLauncher?.launch(customTabsIntent, options) + } else { + Log.d(TAG, "setup: no CCT intent extra found") } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + Log.d(TAG, "onCreate") + customTabsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + Log.d(TAG, "launcher result received -> finish()") + finish() + } setup(intent) } override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) + Log.d(TAG, "onNewIntent") setup(intent) } - override fun onPause() { - super.onPause() - intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID)?.let { browserId -> - sendCustomTabsEvent(lifecycleScope, browserId, EVENT_CUSTOM_TABS_PAUSED) - } - } - - override fun onResume() { - super.onResume() - intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID)?.let { browserId -> - sendCustomTabsEvent(lifecycleScope, browserId, EVENT_CUSTOM_TABS_RESUMED) - } - } - override fun onDestroy() { + Log.d(TAG, "onDestroy -> emitting EVENT_CUSTOM_TABS_DESTROYED") intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID)?.let { browserId -> val customScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val deferred = CompletableDeferred() From c61170547932d3ae31018fb63c960e04237ba15e Mon Sep 17 00:00:00 2001 From: Rui Mendes Date: Tue, 7 Jul 2026 15:33:12 +0100 Subject: [PATCH 2/3] remove logs --- .../helpers/OSIABCustomTabsSessionHelper.kt | 14 -------------- .../routeradapters/OSIABCustomTabsRouterAdapter.kt | 13 ------------- .../views/OSIABCustomTabsControllerActivity.kt | 13 +------------ 3 files changed, 1 insertion(+), 39 deletions(-) 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 df80cac..7e595c8 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 @@ -8,7 +8,6 @@ import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle -import android.util.Log import androidx.browser.customtabs.CustomTabsCallback import androidx.browser.customtabs.CustomTabsClient import androidx.browser.customtabs.CustomTabsServiceConnection @@ -19,8 +18,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.launch -private const val TAG = "OSIABSession" - class OSIABCustomTabsSessionHelper: OSIABCustomTabsSessionHelperInterface { private fun getDefaultCustomTabsPackageName(context: Context): String? { val activityIntent = Intent(Intent.ACTION_VIEW, Uri.parse("http://")) @@ -77,23 +74,12 @@ class OSIABCustomTabsSessionHelper: OSIABCustomTabsSessionHelperInterface { override fun onNavigationEvent(navigationEvent: Int, extras: Bundle?) { super.onNavigationEvent(navigationEvent, extras) - Log.d(TAG, "onNavigationEvent: code=$navigationEvent (${navEventName(navigationEvent)})") if (navigationEvent == NAVIGATION_FINISHED) { lifecycleScope.launch { OSIABEvents.postEvent(OSIABEvents.BrowserPageLoaded(browserId)) } } } - - private fun navEventName(code: Int): String = when (code) { - NAVIGATION_STARTED -> "NAVIGATION_STARTED" - NAVIGATION_FINISHED -> "NAVIGATION_FINISHED" - NAVIGATION_FAILED -> "NAVIGATION_FAILED" - NAVIGATION_ABORTED -> "NAVIGATION_ABORTED" - TAB_SHOWN -> "TAB_SHOWN" - TAB_HIDDEN -> "TAB_HIDDEN" - else -> "UNKNOWN" - } } override suspend fun generateNewCustomTabsSession( 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 a0b518e..293e905 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 @@ -5,7 +5,6 @@ package com.outsystems.plugins.inappbrowser.osinappbrowserlib.routeradapters import android.content.Context import android.content.Intent import android.net.Uri -import android.util.Log import androidx.browser.customtabs.CustomTabsIntent import androidx.browser.customtabs.CustomTabsSession import com.outsystems.plugins.inappbrowser.osinappbrowserlib.OSIABEvents @@ -40,16 +39,11 @@ class OSIABCustomTabsRouterAdapter( private val browserId = UUID.randomUUID().toString() - private companion object { - const val TAG = "OSIABRouter" - } - // for the browserPageLoaded event, which we only want to trigger on the first URL loaded in the CustomTabs instance private var isFirstLoad = true private var isFinished = false override fun close(completionHandler: (Boolean) -> Unit) { - Log.d(TAG, "close called (isFinished=$isFinished, browserId=$browserId)") if (isFinished) { completionHandler(true) return @@ -59,7 +53,6 @@ class OSIABCustomTabsRouterAdapter( closeEventJob = flowHelper.listenToEvents(browserId, lifecycleScope) { event -> if(event is OSIABEvents.OSIABCustomTabsEvent && event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED) { - Log.d(TAG, "close: DESTROYED received -> completionHandler(true)") completionHandler(true) closeEventJob?.cancel() } @@ -114,7 +107,6 @@ class OSIABCustomTabsRouterAdapter( } override fun handleOpen(url: String, completionHandler: (Boolean) -> Unit) { - Log.d(TAG, "handleOpen: url=$url, browserId=$browserId") lifecycleScope.launch { try { val uri = Uri.parse(url) @@ -125,7 +117,6 @@ class OSIABCustomTabsRouterAdapter( flowHelper, customTabsSessionCallback = { if(it == null) { - Log.d(TAG, "handleOpen: session is null -> completionHandler(false)") completionHandler(false) return@generateNewCustomTabsSession } @@ -133,14 +124,12 @@ class OSIABCustomTabsRouterAdapter( } ) } catch (e: Exception) { - Log.d(TAG, "handleOpen: exception ${e.message}") completionHandler(false) } } } private fun openCustomTabsIntent(session: CustomTabsSession, uri: Uri, completionHandler: (Boolean) -> Unit) { - Log.d(TAG, "openCustomTabsIntent: uri=$uri") val customTabsIntent = buildCustomTabsIntent(session) customTabsIntent.intent.data = uri @@ -149,7 +138,6 @@ class OSIABCustomTabsRouterAdapter( when (event) { is OSIABEvents.OSIABCustomTabsEvent -> { if(event.action == OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED) { - Log.d(TAG, "openCustomTabsIntent: DESTROYED -> onBrowserFinished()") isFinished = true onBrowserFinished() eventsJob?.cancel() @@ -157,7 +145,6 @@ class OSIABCustomTabsRouterAdapter( } is OSIABEvents.BrowserPageLoaded -> { if (isFirstLoad) { - Log.d(TAG, "openCustomTabsIntent: BrowserPageLoaded -> onBrowserPageLoaded()") onBrowserPageLoaded() isFirstLoad = false } diff --git a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt index 0000dba..fb27621 100644 --- a/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt +++ b/src/main/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/views/OSIABCustomTabsControllerActivity.kt @@ -2,7 +2,6 @@ package com.outsystems.plugins.inappbrowser.osinappbrowserlib.views import android.content.Intent import android.os.Bundle -import android.util.Log import android.view.WindowManager import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts @@ -22,7 +21,6 @@ import kotlinx.coroutines.launch class OSIABCustomTabsControllerActivity: AppCompatActivity() { companion object { - private const val TAG = "OSIABCT" const val EVENT_CUSTOM_TABS_DESTROYED = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EVENT_CUSTOM_TABS_DESTROYED" const val ACTION_CLOSE_CUSTOM_TABS = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.ACTION_CLOSE_CUSTOM_TABS" const val EXTRA_CUSTOM_TABS_INTENT = "com.outsystems.plugins.inappbrowser.osinappbrowserlib.EXTRA_CUSTOM_TABS_INTENT" @@ -35,14 +33,12 @@ class OSIABCustomTabsControllerActivity: AppCompatActivity() { private fun setup(intent: Intent) { - Log.d(TAG, "setup: hasLaunchedCustomTabs=$hasLaunchedCustomTabs, doClose=${intent.getBooleanExtra(ACTION_CLOSE_CUSTOM_TABS, false)}") window.setFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) if (intent.getBooleanExtra(ACTION_CLOSE_CUSTOM_TABS, false)) { - Log.d(TAG, "setup: close action -> finish()") finish() return } @@ -58,19 +54,14 @@ class OSIABCustomTabsControllerActivity: AppCompatActivity() { } else { null } - Log.d(TAG, "setup: launching CCT (enter=$enterAnimRes, exit=$exitAnimRes)") customTabsLauncher?.launch(customTabsIntent, options) - } else { - Log.d(TAG, "setup: no CCT intent extra found") } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - Log.d(TAG, "onCreate") customTabsLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - Log.d(TAG, "launcher result received -> finish()") finish() } setup(intent) @@ -78,12 +69,10 @@ class OSIABCustomTabsControllerActivity: AppCompatActivity() { override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - Log.d(TAG, "onNewIntent") setup(intent) } override fun onDestroy() { - Log.d(TAG, "onDestroy -> emitting EVENT_CUSTOM_TABS_DESTROYED") intent.getStringExtra(OSIABEvents.EXTRA_BROWSER_ID)?.let { browserId -> val customScope = CoroutineScope(SupervisorJob() + Dispatchers.Default) val deferred = CompletableDeferred() @@ -114,4 +103,4 @@ class OSIABCustomTabsControllerActivity: AppCompatActivity() { scope.cancel() } } -} \ No newline at end of file +} From 2ecc76fba5c52ba5c430e837449609c40a4e7666 Mon Sep 17 00:00:00 2001 From: Rui Mendes Date: Thu, 9 Jul 2026 10:48:33 +0100 Subject: [PATCH 3/3] align Custom Tabs router tests with new destroyed event --- .../OSIABCustomTabsRouterAdapterTests.kt | 34 ++++++++++++++++--- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/test/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABCustomTabsRouterAdapterTests.kt b/src/test/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABCustomTabsRouterAdapterTests.kt index df33978..2cc80c3 100644 --- a/src/test/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABCustomTabsRouterAdapterTests.kt +++ b/src/test/java/com.outsystems.plugins.inappbrowser/osinappbrowserlib/OSIABCustomTabsRouterAdapterTests.kt @@ -7,10 +7,15 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.net.Uri +import androidx.browser.customtabs.CustomTabsSession +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABCustomTabsSessionHelperInterface import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABCustomTabsSessionHelperMock +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABFlowHelperInterface import com.outsystems.plugins.inappbrowser.osinappbrowserlib.helpers.OSIABFlowHelperMock import com.outsystems.plugins.inappbrowser.osinappbrowserlib.models.OSIABCustomTabsOptions import com.outsystems.plugins.inappbrowser.osinappbrowserlib.routeradapters.OSIABCustomTabsRouterAdapter +import com.outsystems.plugins.inappbrowser.osinappbrowserlib.views.OSIABCustomTabsControllerActivity +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals @@ -56,9 +61,21 @@ class OSIABCustomTabsRouterAdapterTests { @Test - fun test_handleOpen_withInvalidURL_returnsFalse() { + fun test_handleOpen_whenNoCustomTabsAvailable_returnsFalse() { runTest(StandardTestDispatcher()) { val context = mockContext(useValidURL = false, ableToOpenURL = false) + val nullSessionHelper = object : OSIABCustomTabsSessionHelperInterface { + override suspend fun generateNewCustomTabsSession( + browserId: String, + context: Context, + lifecycleScope: CoroutineScope, + flowHelper: OSIABFlowHelperInterface, + customTabsSessionCallback: (CustomTabsSession?) -> Unit + ) { + customTabsSessionCallback(null) + } + } + val sut = OSIABCustomTabsRouterAdapter( context = context, lifecycleScope = this, @@ -66,10 +83,10 @@ class OSIABCustomTabsRouterAdapterTests { options = options, onBrowserPageLoaded = {}, onBrowserFinished = {}, - customTabsSessionHelper = OSIABCustomTabsSessionHelperMock(), + customTabsSessionHelper = nullSessionHelper, ) - sut.handleOpen("invalid_url") { success -> + sut.handleOpen(uri.toString()) { success -> assertFalse(success) } } @@ -131,10 +148,17 @@ class OSIABCustomTabsRouterAdapterTests { } @Test - fun test_handleOpen_withValidURL_launchesCustomTab_when_browserFinished_then_browserFinishedTriggered() { + fun test_handleOpen_withValidURL_launchesCustomTab_when_customTabsDestroyed_then_browserFinishedTriggered() { runTest(StandardTestDispatcher()) { val context = mockContext(useValidURL = true, ableToOpenURL = true) - val flowHelperMock = OSIABFlowHelperMock().apply { events = listOf(OSIABEvents.BrowserFinished("")) } + val flowHelperMock = OSIABFlowHelperMock().apply { + events = listOf( + OSIABEvents.OSIABCustomTabsEvent( + browserId = "", + action = OSIABCustomTabsControllerActivity.EVENT_CUSTOM_TABS_DESTROYED + ) + ) + } val sut = OSIABCustomTabsRouterAdapter( context = context, lifecycleScope = this,