From 49a39643392ee50a1a0224948f92cd2cc20d233d Mon Sep 17 00:00:00 2001 From: Rui Mendes Date: Thu, 2 Jul 2026 15:33:48 +0100 Subject: [PATCH 1/2] fix(browser): only fire browserFinished when the Custom Tab actually terminates --- .../capacitorjs/plugins/browser/Browser.java | 42 +++++++++-------- .../browser/BrowserControllerActivity.java | 34 +++++++------- .../plugins/browser/EventGroup.java | 45 ------------------- 3 files changed, 42 insertions(+), 79 deletions(-) delete mode 100644 browser/android/src/main/java/com/capacitorjs/plugins/browser/EventGroup.java diff --git a/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java b/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java index fff0ddc43..22188fab6 100644 --- a/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java +++ b/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java @@ -7,6 +7,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.browser.customtabs.*; @@ -43,7 +44,8 @@ interface BrowserEventListener { private CustomTabsClient customTabsClient; private CustomTabsSession browserSession; private boolean isInitialLoad = false; - private EventGroup group; + @Nullable + private ActivityResultLauncher customTabLauncher; private CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { @@ -61,7 +63,6 @@ public void onServiceDisconnected(ComponentName name) {} */ public Browser(@NonNull Context context) { this.context = context; - this.group = new EventGroup(this::handleGroupCompletion); } /** @@ -81,6 +82,16 @@ public BrowserEventListener getBrowserEventListenerListener() { return browserEventListener; } + /** + * Provide the ActivityResultLauncher used to open the Custom Tab. When + * set, the Custom Tab is launched via this launcher so that + * {@link #notifyBrowserFinished()} can be triggered from the launcher's + * result callback (only when the tab activity actually terminates). + */ + public void setCustomTabLauncher(@Nullable ActivityResultLauncher launcher) { + this.customTabLauncher = launcher; + } + /** * Open the browser to the specified URL. * @param url @@ -108,8 +119,12 @@ public void open(Uri url, @Nullable Integer toolbarColor) { tabsIntent.intent.putExtra(Intent.EXTRA_REFERRER, Uri.parse(Intent.URI_ANDROID_APP_SCHEME + "//" + context.getPackageName())); isInitialLoad = true; - group.reset(); - tabsIntent.launchUrl(context, url); + if (customTabLauncher != null) { + tabsIntent.intent.setData(url); + customTabLauncher.launch(tabsIntent.intent); + } else { + tabsIntent.launchUrl(context, url); + } } /** @@ -120,9 +135,7 @@ public boolean bindService() { if (null == customTabPackageName) { customTabPackageName = FALLBACK_CUSTOM_TAB_PACKAGE_NAME; } - boolean result = CustomTabsClient.bindCustomTabsService(context, customTabPackageName, connection); - group.leave(); - return result; + return CustomTabsClient.bindCustomTabsService(context, customTabPackageName, connection); } /** @@ -130,7 +143,6 @@ public boolean bindService() { */ public void unbindService() { context.unbindService(connection); - group.enter(); } private void handledNavigationEvent(int navigationEvent) { @@ -143,19 +155,13 @@ private void handledNavigationEvent(int navigationEvent) { isInitialLoad = false; } break; - case CustomTabsCallback.TAB_HIDDEN: - group.leave(); - break; - case CustomTabsCallback.TAB_SHOWN: - group.enter(); - break; } } - private void handleGroupCompletion() { - // events such as TAB_HIDDEN and onPause can occur for multiple reasons and in - // different sequences so there is no single point to fire this. so we rely on the - // event group to track when it is safe to assume that the browser is done. + public void notifyBrowserFinished() { + // Notify listeners that the browser session has finished. Called by the + // host activity when the Custom Tab activity actually returns a result + // (i.e. the tab was truly dismissed, not just backgrounded or minimised). if (browserEventListener != null) { browserEventListener.onBrowserEvent(BROWSER_FINISHED); } diff --git a/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java b/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java index 74fd599c1..7fc834fb4 100644 --- a/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java +++ b/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java @@ -1,19 +1,31 @@ package com.capacitorjs.plugins.browser; -import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import androidx.activity.ComponentActivity; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.Nullable; -public class BrowserControllerActivity extends Activity { +public class BrowserControllerActivity extends ComponentActivity { - private boolean isCustomTabsOpen = false; + private ActivityResultLauncher customTabLauncher; + private Browser implementation; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - isCustomTabsOpen = false; + + customTabLauncher = registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (implementation != null) { + implementation.notifyBrowserFinished(); + } + finish(); + } + ); if (BrowserPlugin.browserControllerListener != null) { BrowserPlugin.browserControllerListener.onControllerReady(this); @@ -28,25 +40,15 @@ protected void onNewIntent(Intent intent) { } } - @Override - protected void onResume() { - super.onResume(); - if (isCustomTabsOpen) { - isCustomTabsOpen = false; - finish(); - } else { - isCustomTabsOpen = true; - } - } - public void open(Browser implementation, Uri url, Integer toolbarColor) { + this.implementation = implementation; + implementation.setCustomTabLauncher(customTabLauncher); implementation.open(url, toolbarColor); } @Override protected void onDestroy() { super.onDestroy(); - isCustomTabsOpen = false; BrowserPlugin.setBrowserControllerListener(null); } } diff --git a/browser/android/src/main/java/com/capacitorjs/plugins/browser/EventGroup.java b/browser/android/src/main/java/com/capacitorjs/plugins/browser/EventGroup.java deleted file mode 100644 index 2c9541edc..000000000 --- a/browser/android/src/main/java/com/capacitorjs/plugins/browser/EventGroup.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.capacitorjs.plugins.browser; - -/** - * Simple class to handle indeterminate sequence of events. Not thread safe. - */ -class EventGroup { - - interface EventGroupCompletion { - void onGroupCompletion(); - } - - private int count; - private boolean isComplete; - private EventGroupCompletion completion; - - public EventGroup(EventGroupCompletion onCompletion) { - super(); - count = 0; - isComplete = false; - completion = onCompletion; - } - - public void enter() { - count++; - } - - public void leave() { - count--; - checkForCompletion(); - } - - public void reset() { - count = 0; - isComplete = false; - } - - private void checkForCompletion() { - if (count <= 0) { - if (isComplete == false && completion != null) { - completion.onGroupCompletion(); - } - isComplete = true; - } - } -} From 1276c03066d982968f08ff7d1e1847c99da365c0 Mon Sep 17 00:00:00 2001 From: Rui Mendes Date: Fri, 3 Jul 2026 16:15:55 +0100 Subject: [PATCH 2/2] chore(browser): format java code --- .../com/capacitorjs/plugins/browser/Browser.java | 2 ++ .../plugins/browser/BrowserControllerActivity.java | 13 +++++-------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java b/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java index 22188fab6..937ec3737 100644 --- a/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java +++ b/browser/android/src/main/java/com/capacitorjs/plugins/browser/Browser.java @@ -44,8 +44,10 @@ interface BrowserEventListener { private CustomTabsClient customTabsClient; private CustomTabsSession browserSession; private boolean isInitialLoad = false; + @Nullable private ActivityResultLauncher customTabLauncher; + private CustomTabsServiceConnection connection = new CustomTabsServiceConnection() { @Override public void onCustomTabsServiceConnected(ComponentName name, CustomTabsClient client) { diff --git a/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java b/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java index 7fc834fb4..4caf3c578 100644 --- a/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java +++ b/browser/android/src/main/java/com/capacitorjs/plugins/browser/BrowserControllerActivity.java @@ -17,15 +17,12 @@ public class BrowserControllerActivity extends ComponentActivity { protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - customTabLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (implementation != null) { - implementation.notifyBrowserFinished(); - } - finish(); + customTabLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), (result) -> { + if (implementation != null) { + implementation.notifyBrowserFinished(); } - ); + finish(); + }); if (BrowserPlugin.browserControllerListener != null) { BrowserPlugin.browserControllerListener.onControllerReady(this);