From 71bb93889871fcd8684fccf98fba14f10c86d7e3 Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 26 Jan 2026 18:26:41 -0800 Subject: [PATCH 01/12] Add new init method to BrowserSwitchClient that takes in an ActivityResultRegistry --- .../api/AuthTabInternalClient.kt | 1 + .../api/BrowserSwitchClient.java | 103 ++++++++--- build.gradle | 4 +- demo/build.gradle | 4 +- .../browserswitch/demo/BrowserSwitchDemo.kt | 173 ++++++++++++++++++ .../api/browserswitch/demo/ComposeActivity.kt | 47 +++-- .../demo/viewmodel/BrowserSwitchViewModel.kt | 3 +- 7 files changed, 287 insertions(+), 48 deletions(-) create mode 100644 demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt diff --git a/browser-switch/src/main/java/com/braintreepayments/api/AuthTabInternalClient.kt b/browser-switch/src/main/java/com/braintreepayments/api/AuthTabInternalClient.kt index b2b511be..c8d8589a 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/AuthTabInternalClient.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/AuthTabInternalClient.kt @@ -55,6 +55,7 @@ internal class AuthTabInternalClient( launchCustomTabs(context, url, launchType) } } + private fun launchCustomTabs(context: Context, url: Uri, launchType: LaunchType?) { val customTabsIntent = customTabsIntentBuilder.build() when (launchType) { diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index de13c4f6..67e0b15e 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -6,8 +6,10 @@ import android.content.Intent; import android.net.Uri; +import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.ActivityResultRegistry; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; @@ -27,9 +29,34 @@ public class BrowserSwitchClient { private ActivityResultLauncher authTabLauncher; private BrowserSwitchRequest pendingAuthTabRequest; + final String registryKey = "BrowserSwitchActivityRegistryKey"; + @Nullable private BrowserSwitchFinalResult authTabCallbackResult; + ActivityResultCallback authTabCallback = new ActivityResultCallback<>() { + @Override + public void onActivityResult(AuthTabIntent.AuthResult result) { + BrowserSwitchFinalResult finalResult; + switch (result.resultCode) { + case AuthTabIntent.RESULT_OK: + if (result.resultUri != null && pendingAuthTabRequest != null) { + finalResult = new BrowserSwitchFinalResult.Success( + result.resultUri, + pendingAuthTabRequest + ); + } else { + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + break; + default: + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + authTabCallbackResult = finalResult; + pendingAuthTabRequest = null; + } + }; + /** * Construct a client that manages browser switching with Chrome Custom Tabs fallback only. * This constructor does not initialize Auth Tab support. For Auth Tab functionality, @@ -73,6 +100,11 @@ public BrowserSwitchClient(@NonNull ActivityResultCaller caller) { initializeAuthTabLauncher(caller); } + public BrowserSwitchClient(@NonNull ActivityResultRegistry registry) { + this(new BrowserSwitchInspector(), new AuthTabInternalClient()); + initializeAuthTabLauncher(registry); + } + @VisibleForTesting BrowserSwitchClient(BrowserSwitchInspector browserSwitchInspector, AuthTabInternalClient authTabInternalClient) { @@ -95,32 +127,52 @@ public BrowserSwitchClient(@NonNull ActivityResultCaller caller) { * @param caller The ActivityResultCaller (Activity or Fragment) used to initialize the Auth Tab launcher */ private void initializeAuthTabLauncher(@NonNull ActivityResultCaller caller) { + authTabLauncher = AuthTabIntent.registerActivityResultLauncher( + caller, + result -> { + BrowserSwitchFinalResult finalResult; + switch (result.resultCode) { + case AuthTabIntent.RESULT_OK: + if (result.resultUri != null && pendingAuthTabRequest != null) { + finalResult = new BrowserSwitchFinalResult.Success( + result.resultUri, + pendingAuthTabRequest + ); + } else { + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + break; + default: + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + authTabCallbackResult = finalResult; + pendingAuthTabRequest = null; + } + ); + } - this.authTabLauncher = AuthTabIntent.registerActivityResultLauncher( - caller, - result -> { - BrowserSwitchFinalResult finalResult; - switch (result.resultCode) { - case AuthTabIntent.RESULT_OK: - if (result.resultUri != null && pendingAuthTabRequest != null) { - finalResult = new BrowserSwitchFinalResult.Success( - result.resultUri, - pendingAuthTabRequest - ); - } else { - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - break; - default: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + private void initializeAuthTabLauncher(@NonNull ActivityResultRegistry registry) { + authTabLauncher = registry.register(registryKey, new AuthTabIntent.AuthenticateUserResultContract(), result -> { + BrowserSwitchFinalResult finalResult; + switch (result.resultCode) { + case AuthTabIntent.RESULT_OK: + if (result.resultUri != null && pendingAuthTabRequest != null) { + finalResult = new BrowserSwitchFinalResult.Success( + result.resultUri, + pendingAuthTabRequest + ); + } else { + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; } - this.authTabCallbackResult = finalResult; - pendingAuthTabRequest = null; - } - ); + break; + default: + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + authTabCallbackResult = finalResult; + pendingAuthTabRequest = null; + }); } - /** * Restores a pending request after process kill or app restart. * @@ -150,6 +202,7 @@ public BrowserSwitchStartResult start(@NonNull Activity activity, @NonNull BrowserSwitchOptions browserSwitchOptions) { return start(activity, browserSwitchOptions, false); } + /** * Open a browser or Auth Tab with a given set of {@link BrowserSwitchOptions} from an Android activity. * @@ -306,6 +359,10 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull return BrowserSwitchFinalResult.NoResult.INSTANCE; } + void cleanup() { + authTabLauncher.unregister(); + } + /** * Checks if Auth Tab is supported on this device and if the launcher has been initialized. * @param context The application context @@ -316,4 +373,4 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull boolean isAuthTabSupported(Context context) { return authTabLauncher != null && authTabInternalClient.isAuthTabSupported(context); } -} \ No newline at end of file +} diff --git a/build.gradle b/build.gradle index 372388e5..b7178108 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ buildscript { ext.deps = [ 'annotation' : 'androidx.annotation:annotation:1.7.0', 'appcompat' : 'androidx.appcompat:appcompat:1.6.0', - 'browser' : 'androidx.browser:browser:1.9.0', + 'browser' : 'androidx.browser:browser:1.10.0-alpha02', 'kotlin' : 'org.jetbrains.kotlin:kotlin-stdlib:1.9.20', // test dependencies @@ -38,7 +38,7 @@ buildscript { plugins { id 'io.github.gradle-nexus.publish-plugin' version '1.1.0' id 'org.jetbrains.dokka' version '1.9.10' - id 'org.jetbrains.kotlin.android' version '1.8.10' apply false + id 'org.jetbrains.kotlin.android' version '1.9.10' apply false id 'io.gitlab.arturbosch.detekt' version '1.23.6' } diff --git a/demo/build.gradle b/demo/build.gradle index 22dfc315..8dc7f436 100644 --- a/demo/build.gradle +++ b/demo/build.gradle @@ -39,7 +39,7 @@ android { compose true } composeOptions { - kotlinCompilerExtensionVersion '1.4.3' + kotlinCompilerExtensionVersion '1.5.3' } packagingOptions { resources { @@ -58,6 +58,8 @@ dependencies { implementation platform('androidx.compose:compose-bom:2023.03.00') implementation 'androidx.compose.material3:material3' implementation 'androidx.core:core-ktx:1.13.1' + implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1" + implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.10.0' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt new file mode 100644 index 00000000..07d055e6 --- /dev/null +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt @@ -0,0 +1,173 @@ +package com.braintreepayments.api.browserswitch.demo + +import android.app.Activity +import android.content.Context +import android.content.ContextWrapper +import android.net.Uri +import androidx.activity.ComponentActivity +import androidx.activity.compose.LocalActivityResultRegistryOwner +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.safeGesturesPadding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.core.net.toUri +import androidx.lifecycle.compose.LifecycleResumeEffect +import androidx.lifecycle.viewmodel.compose.viewModel +import com.braintreepayments.api.BrowserSwitchClient +import com.braintreepayments.api.BrowserSwitchFinalResult +import com.braintreepayments.api.BrowserSwitchOptions +import com.braintreepayments.api.BrowserSwitchStartResult +import com.braintreepayments.api.browserswitch.demo.utils.PendingRequestStore +import com.braintreepayments.api.browserswitch.demo.viewmodel.BrowserSwitchViewModel +import org.json.JSONObject + +private const val RETURN_URL_SCHEME = "my-custom-url-scheme-standard" + +@Composable +fun MainContent() { + val viewModel: BrowserSwitchViewModel = viewModel { BrowserSwitchViewModel() } + val browserSwitchClient: BrowserSwitchClient = LocalActivityResultRegistryOwner.current?.let { BrowserSwitchClient(it.activityResultRegistry) } + ?: BrowserSwitchClient() + + val context = LocalContext.current + val activity = context.findActivity() +// PendingRequestStore.get(context)?.let { pendingRequest -> +// try { +// browserSwitchClient.restorePendingRequest(pendingRequest) +// } catch (e: BrowserSwitchException) { +// Log.e("ComposeActivity", "Failed to restore pending request", e) +// PendingRequestStore.clear(context) +// } +// } + + Column(modifier = Modifier.safeGesturesPadding()) { + BrowserSwitchButton { + activity?.let { startBrowserSwitch(it, viewModel, browserSwitchClient) } + } + BrowserSwitchResult(viewModel = viewModel) + } + + LifecycleResumeEffect(Unit) { + val intent = (context as ComponentActivity).intent + PendingRequestStore.get(context)?.let { startedRequest -> + val completeRequestResult = browserSwitchClient.completeRequest(intent, startedRequest) + handleBrowserSwitchResult(viewModel, completeRequestResult) + PendingRequestStore.clear(context) + intent.data = null + } + + onPauseOrDispose { lifecycle } + } +} + +fun Context.findActivity(): Activity? = when (this) { + is Activity -> this + is ContextWrapper -> baseContext.findActivity() + else -> null +} + +private fun handleBrowserSwitchResult(viewModel: BrowserSwitchViewModel, result: BrowserSwitchFinalResult) { + when (result) { + is BrowserSwitchFinalResult.Success -> + viewModel.browserSwitchFinalResult = result + + is BrowserSwitchFinalResult.NoResult -> + viewModel.browserSwitchError = Exception("User did not complete browser switch") + + is BrowserSwitchFinalResult.Failure -> + viewModel.browserSwitchError = result.error + } +} + +private fun startBrowserSwitch( + activity: Activity, + viewModel: BrowserSwitchViewModel, + browserSwitchClient: BrowserSwitchClient +) { + val url = buildBrowserSwitchUrl() + val browserSwitchOptions = BrowserSwitchOptions() + .metadata(buildMetadataObject()) + .requestCode(1) + .url(url) + .launchAsNewTask(false) + .returnUrlScheme(RETURN_URL_SCHEME) + + when (val startResult = browserSwitchClient.start(activity, browserSwitchOptions)) { + is BrowserSwitchStartResult.Started -> { + PendingRequestStore.put(activity.applicationContext, startResult.pendingRequest) + } + is BrowserSwitchStartResult.Failure -> + viewModel.browserSwitchError = startResult.error + } +} + +private fun buildBrowserSwitchUrl(): Uri? { + val url = "https://braintree.github.io/popup-bridge-example/" + + "this_launches_in_popup.html?popupBridgeReturnUrlPrefix=$RETURN_URL_SCHEME://" + return url.toUri() +} + +private fun buildMetadataObject(): JSONObject? { + return JSONObject().put("test_key", "test_value") +} + +@Composable +private fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { + val uiState = viewModel.uiState.collectAsState().value + (uiState.browserSwitchFinalResult as? BrowserSwitchFinalResult.Success)?.let { + BrowserSwitchSuccess(result = it) + } + uiState.browserSwitchError?.let { BrowserSwitchError(exception = it) } +} + +@Composable +private fun BrowserSwitchButton(onClick: () -> Unit) { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = onClick + ) { + Text(text = "Start Browser Switch") + } +} + +@Composable +private fun BrowserSwitchSuccess(result: BrowserSwitchFinalResult.Success) { + val color = result.returnUrl.getQueryParameter("color") + val selectedColorString = "Selected color: $color" + val metadataOutput = result.requestMetadata?.getString("test_key")?.let { "test_key=$it" } + Column(modifier = Modifier.padding(10.dp)) { + Text( + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + text = "Browser Switch Successful" + ) + Text(text = selectedColorString, color = Color.White) + metadataOutput?.let { + Text(text = "Metadata: $it", color = Color.White) + } + } +} + +@Composable +private fun BrowserSwitchError(exception: Exception) { + Column(modifier = Modifier.padding(10.dp)) { + Text( + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + text = "Browser Switch Error" + ) + exception.message?.let { Text(text = it, color = Color.White) } + } +} diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt index e1351488..0e9bb0f8 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt @@ -25,8 +25,9 @@ import com.braintreepayments.api.BrowserSwitchFinalResult import com.braintreepayments.api.BrowserSwitchOptions import com.braintreepayments.api.BrowserSwitchStartResult import com.braintreepayments.api.browserswitch.demo.utils.PendingRequestStore -import com.braintreepayments.api.demo.viewmodel.BrowserSwitchViewModel +import com.braintreepayments.api.browserswitch.demo.viewmodel.BrowserSwitchViewModel import org.json.JSONObject +import androidx.core.net.toUri class ComposeActivity : ComponentActivity() { @@ -35,6 +36,20 @@ class ComposeActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContent { + Column(modifier = Modifier.safeGesturesPadding()) { +// BrowserSwitchButton { +// startBrowserSwitch() +// } +// BrowserSwitchResult(viewModel = viewModel) + + MainContent() + } + } + } + + override fun onStart() { + super.onStart() browserSwitchClient = BrowserSwitchClient(this) PendingRequestStore.get(this)?.let { pendingRequest -> try { @@ -44,24 +59,16 @@ class ComposeActivity : ComponentActivity() { PendingRequestStore.clear(this) } } - setContent { - Column(modifier = Modifier.safeGesturesPadding()) { - BrowserSwitchButton { - startBrowserSwitch() - } - BrowserSwitchResult(viewModel = viewModel) - } - } } override fun onResume() { super.onResume() - PendingRequestStore.get(this)?.let { startedRequest -> - val completeRequestResult = browserSwitchClient.completeRequest(intent, startedRequest) - handleBrowserSwitchResult(completeRequestResult) - PendingRequestStore.clear(this) - intent.data = null - } +// PendingRequestStore.get(this)?.let { startedRequest -> +// val completeRequestResult = browserSwitchClient.completeRequest(intent, startedRequest) +// handleBrowserSwitchResult(completeRequestResult) +// PendingRequestStore.clear(this) +// intent.data = null +// } } private fun handleBrowserSwitchResult(result: BrowserSwitchFinalResult) { @@ -98,7 +105,7 @@ class ComposeActivity : ComponentActivity() { private fun buildBrowserSwitchUrl(): Uri? { val url = "https://braintree.github.io/popup-bridge-example/" + "this_launches_in_popup.html?popupBridgeReturnUrlPrefix=$RETURN_URL_SCHEME://" - return Uri.parse(url) + return url.toUri() } private fun buildMetadataObject(): JSONObject? { @@ -111,7 +118,7 @@ class ComposeActivity : ComponentActivity() { } @Composable -fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { +private fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { val uiState = viewModel.uiState.collectAsState().value (uiState.browserSwitchFinalResult as? BrowserSwitchFinalResult.Success)?.let { BrowserSwitchSuccess(result = it) @@ -120,7 +127,7 @@ fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { } @Composable -fun BrowserSwitchButton(onClick: () -> Unit) { +private fun BrowserSwitchButton(onClick: () -> Unit) { Button( modifier = Modifier.fillMaxWidth(), onClick = onClick @@ -130,7 +137,7 @@ fun BrowserSwitchButton(onClick: () -> Unit) { } @Composable -fun BrowserSwitchSuccess(result: BrowserSwitchFinalResult.Success) { +private fun BrowserSwitchSuccess(result: BrowserSwitchFinalResult.Success) { val color = result.returnUrl.getQueryParameter("color") val selectedColorString = "Selected color: $color" val metadataOutput = result.requestMetadata?.getString("test_key")?.let { "test_key=$it" } @@ -149,7 +156,7 @@ fun BrowserSwitchSuccess(result: BrowserSwitchFinalResult.Success) { } @Composable -fun BrowserSwitchError(exception: Exception) { +private fun BrowserSwitchError(exception: Exception) { Column(modifier = Modifier.padding(10.dp)) { Text( fontSize = 20.sp, diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/viewmodel/BrowserSwitchViewModel.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/viewmodel/BrowserSwitchViewModel.kt index 984eed79..b2a9b970 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/viewmodel/BrowserSwitchViewModel.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/viewmodel/BrowserSwitchViewModel.kt @@ -1,8 +1,7 @@ -package com.braintreepayments.api.demo.viewmodel +package com.braintreepayments.api.browserswitch.demo.viewmodel import androidx.lifecycle.ViewModel import com.braintreepayments.api.BrowserSwitchFinalResult -import com.braintreepayments.api.browserswitch.demo.viewmodel.UiState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow From 26c9338a106d6fb0742832981896c2325da66f7c Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 26 Jan 2026 18:29:08 -0800 Subject: [PATCH 02/12] refactor --- .../api/BrowserSwitchClient.java | 46 +++---------------- 1 file changed, 7 insertions(+), 39 deletions(-) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index 67e0b15e..4cd7855b 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -128,49 +128,17 @@ public BrowserSwitchClient(@NonNull ActivityResultRegistry registry) { */ private void initializeAuthTabLauncher(@NonNull ActivityResultCaller caller) { authTabLauncher = AuthTabIntent.registerActivityResultLauncher( - caller, - result -> { - BrowserSwitchFinalResult finalResult; - switch (result.resultCode) { - case AuthTabIntent.RESULT_OK: - if (result.resultUri != null && pendingAuthTabRequest != null) { - finalResult = new BrowserSwitchFinalResult.Success( - result.resultUri, - pendingAuthTabRequest - ); - } else { - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - break; - default: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - authTabCallbackResult = finalResult; - pendingAuthTabRequest = null; - } + caller, + authTabCallback ); } private void initializeAuthTabLauncher(@NonNull ActivityResultRegistry registry) { - authTabLauncher = registry.register(registryKey, new AuthTabIntent.AuthenticateUserResultContract(), result -> { - BrowserSwitchFinalResult finalResult; - switch (result.resultCode) { - case AuthTabIntent.RESULT_OK: - if (result.resultUri != null && pendingAuthTabRequest != null) { - finalResult = new BrowserSwitchFinalResult.Success( - result.resultUri, - pendingAuthTabRequest - ); - } else { - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - break; - default: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - authTabCallbackResult = finalResult; - pendingAuthTabRequest = null; - }); + authTabLauncher = registry.register( + registryKey, + new AuthTabIntent.AuthenticateUserResultContract(), + authTabCallback + ); } /** From 4d5d30379eb6108855aafa81a7eb6a57b9e06321 Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 26 Jan 2026 19:02:44 -0800 Subject: [PATCH 03/12] cleanup demo --- .../api/browserswitch/demo/ComposeActivity.kt | 149 ------------------ .../{BrowserSwitchDemo.kt => MainContent.kt} | 18 ++- 2 files changed, 10 insertions(+), 157 deletions(-) rename demo/src/main/java/com/braintreepayments/api/browserswitch/demo/{BrowserSwitchDemo.kt => MainContent.kt} (93%) diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt index 0e9bb0f8..a2b0706a 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/ComposeActivity.kt @@ -1,169 +1,20 @@ package com.braintreepayments.api.browserswitch.demo -import android.net.Uri import android.os.Bundle -import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.activity.viewModels import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeGesturesPadding -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.braintreepayments.api.BrowserSwitchClient -import com.braintreepayments.api.BrowserSwitchException -import com.braintreepayments.api.BrowserSwitchFinalResult -import com.braintreepayments.api.BrowserSwitchOptions -import com.braintreepayments.api.BrowserSwitchStartResult -import com.braintreepayments.api.browserswitch.demo.utils.PendingRequestStore -import com.braintreepayments.api.browserswitch.demo.viewmodel.BrowserSwitchViewModel -import org.json.JSONObject -import androidx.core.net.toUri class ComposeActivity : ComponentActivity() { - private val viewModel by viewModels() - private lateinit var browserSwitchClient: BrowserSwitchClient - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { Column(modifier = Modifier.safeGesturesPadding()) { -// BrowserSwitchButton { -// startBrowserSwitch() -// } -// BrowserSwitchResult(viewModel = viewModel) - MainContent() } } } - - override fun onStart() { - super.onStart() - browserSwitchClient = BrowserSwitchClient(this) - PendingRequestStore.get(this)?.let { pendingRequest -> - try { - browserSwitchClient.restorePendingRequest(pendingRequest) - } catch (e: BrowserSwitchException) { - Log.e("ComposeActivity", "Failed to restore pending request", e) - PendingRequestStore.clear(this) - } - } - } - - override fun onResume() { - super.onResume() -// PendingRequestStore.get(this)?.let { startedRequest -> -// val completeRequestResult = browserSwitchClient.completeRequest(intent, startedRequest) -// handleBrowserSwitchResult(completeRequestResult) -// PendingRequestStore.clear(this) -// intent.data = null -// } - } - - private fun handleBrowserSwitchResult(result: BrowserSwitchFinalResult) { - when (result) { - is BrowserSwitchFinalResult.Success -> - viewModel.browserSwitchFinalResult = result - - is BrowserSwitchFinalResult.NoResult -> - viewModel.browserSwitchError = Exception("User did not complete browser switch") - - is BrowserSwitchFinalResult.Failure -> - viewModel.browserSwitchError = result.error - } - } - - private fun startBrowserSwitch() { - val url = buildBrowserSwitchUrl() - val browserSwitchOptions = BrowserSwitchOptions() - .metadata(buildMetadataObject()) - .requestCode(1) - .url(url) - .launchAsNewTask(false) - .returnUrlScheme(RETURN_URL_SCHEME) - - when (val startResult = browserSwitchClient.start(this, browserSwitchOptions)) { - is BrowserSwitchStartResult.Started -> { - PendingRequestStore.put(this, startResult.pendingRequest) - } - is BrowserSwitchStartResult.Failure -> - viewModel.browserSwitchError = startResult.error - } - } - - private fun buildBrowserSwitchUrl(): Uri? { - val url = "https://braintree.github.io/popup-bridge-example/" + - "this_launches_in_popup.html?popupBridgeReturnUrlPrefix=$RETURN_URL_SCHEME://" - return url.toUri() - } - - private fun buildMetadataObject(): JSONObject? { - return JSONObject().put("test_key", "test_value") - } - - companion object { - private const val RETURN_URL_SCHEME = "my-custom-url-scheme-standard" - } -} - -@Composable -private fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { - val uiState = viewModel.uiState.collectAsState().value - (uiState.browserSwitchFinalResult as? BrowserSwitchFinalResult.Success)?.let { - BrowserSwitchSuccess(result = it) - } - uiState.browserSwitchError?.let { BrowserSwitchError(exception = it) } -} - -@Composable -private fun BrowserSwitchButton(onClick: () -> Unit) { - Button( - modifier = Modifier.fillMaxWidth(), - onClick = onClick - ) { - Text(text = "Start Browser Switch") - } -} - -@Composable -private fun BrowserSwitchSuccess(result: BrowserSwitchFinalResult.Success) { - val color = result.returnUrl.getQueryParameter("color") - val selectedColorString = "Selected color: $color" - val metadataOutput = result.requestMetadata?.getString("test_key")?.let { "test_key=$it" } - Column(modifier = Modifier.padding(10.dp)) { - Text( - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - text = "Browser Switch Successful" - ) - Text(text = selectedColorString, color = Color.White) - metadataOutput?.let { - Text(text = "Metadata: $it", color = Color.White) - } - } -} - -@Composable -private fun BrowserSwitchError(exception: Exception) { - Column(modifier = Modifier.padding(10.dp)) { - Text( - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - text = "Browser Switch Error" - ) - exception.message?.let { Text(text = it, color = Color.White) } - } } diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt similarity index 93% rename from demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt rename to demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt index 07d055e6..d7568164 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/BrowserSwitchDemo.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.net.Uri +import android.util.Log import androidx.activity.ComponentActivity import androidx.activity.compose.LocalActivityResultRegistryOwner import androidx.compose.foundation.layout.Column @@ -24,6 +25,7 @@ import androidx.core.net.toUri import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.viewmodel.compose.viewModel import com.braintreepayments.api.BrowserSwitchClient +import com.braintreepayments.api.BrowserSwitchException import com.braintreepayments.api.BrowserSwitchFinalResult import com.braintreepayments.api.BrowserSwitchOptions import com.braintreepayments.api.BrowserSwitchStartResult @@ -41,14 +43,14 @@ fun MainContent() { val context = LocalContext.current val activity = context.findActivity() -// PendingRequestStore.get(context)?.let { pendingRequest -> -// try { -// browserSwitchClient.restorePendingRequest(pendingRequest) -// } catch (e: BrowserSwitchException) { -// Log.e("ComposeActivity", "Failed to restore pending request", e) -// PendingRequestStore.clear(context) -// } -// } + PendingRequestStore.get(context)?.let { pendingRequest -> + try { + browserSwitchClient.restorePendingRequest(pendingRequest) + } catch (e: BrowserSwitchException) { + Log.e("ComposeActivity", "Failed to restore pending request", e) + PendingRequestStore.clear(context) + } + } Column(modifier = Modifier.safeGesturesPadding()) { BrowserSwitchButton { From 02b017bc89c17e3854ec7a6b9ab0340a9cb9cfa2 Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 26 Jan 2026 19:59:48 -0800 Subject: [PATCH 04/12] refactor --- .../api/BrowserSwitchClient.java | 2 +- .../api/browserswitch/demo/MainContent.kt | 22 +++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index 4cd7855b..844420ea 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -327,7 +327,7 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull return BrowserSwitchFinalResult.NoResult.INSTANCE; } - void cleanup() { + public void cleanup() { authTabLauncher.unregister(); } diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt index d7568164..002639d6 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt @@ -14,6 +14,7 @@ import androidx.compose.foundation.layout.safeGesturesPadding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -22,6 +23,8 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.net.toUri +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect import androidx.lifecycle.compose.LifecycleResumeEffect import androidx.lifecycle.viewmodel.compose.viewModel import com.braintreepayments.api.BrowserSwitchClient @@ -43,12 +46,15 @@ fun MainContent() { val context = LocalContext.current val activity = context.findActivity() - PendingRequestStore.get(context)?.let { pendingRequest -> - try { - browserSwitchClient.restorePendingRequest(pendingRequest) - } catch (e: BrowserSwitchException) { - Log.e("ComposeActivity", "Failed to restore pending request", e) - PendingRequestStore.clear(context) + + LaunchedEffect(true) { + PendingRequestStore.get(context)?.let { pendingRequest -> + try { + browserSwitchClient.restorePendingRequest(pendingRequest) + } catch (e: BrowserSwitchException) { + Log.e("ComposeActivity", "Failed to restore pending request", e) + PendingRequestStore.clear(context) + } } } @@ -60,12 +66,14 @@ fun MainContent() { } LifecycleResumeEffect(Unit) { - val intent = (context as ComponentActivity).intent + val context = (context as ComponentActivity) PendingRequestStore.get(context)?.let { startedRequest -> + val intent = context.intent val completeRequestResult = browserSwitchClient.completeRequest(intent, startedRequest) handleBrowserSwitchResult(viewModel, completeRequestResult) PendingRequestStore.clear(context) intent.data = null + browserSwitchClient.cleanup() } onPauseOrDispose { lifecycle } From 71b52aa70526d62cf7bd1468f55a3a022d72aad5 Mon Sep 17 00:00:00 2001 From: saperi Date: Tue, 27 Jan 2026 13:21:44 -0800 Subject: [PATCH 05/12] Working demo of Auth Tab + Compose --- .../api/BrowserSwitchClient.java | 55 +++++++++++-------- .../api/browserswitch/demo/MainContent.kt | 2 +- 2 files changed, 34 insertions(+), 23 deletions(-) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index 844420ea..6aa1e391 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -34,29 +34,36 @@ public class BrowserSwitchClient { @Nullable private BrowserSwitchFinalResult authTabCallbackResult; + @Nullable + private AuthTabIntent.AuthResult authTabResult; + ActivityResultCallback authTabCallback = new ActivityResultCallback<>() { @Override public void onActivityResult(AuthTabIntent.AuthResult result) { - BrowserSwitchFinalResult finalResult; - switch (result.resultCode) { - case AuthTabIntent.RESULT_OK: - if (result.resultUri != null && pendingAuthTabRequest != null) { - finalResult = new BrowserSwitchFinalResult.Success( - result.resultUri, - pendingAuthTabRequest - ); - } else { - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - break; - default: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - } - authTabCallbackResult = finalResult; - pendingAuthTabRequest = null; + authTabResult = result; } }; + void onAuthTabResult(AuthTabIntent.AuthResult result) { + BrowserSwitchFinalResult finalResult; + switch (result.resultCode) { + case AuthTabIntent.RESULT_OK: + if (result.resultUri != null && pendingAuthTabRequest != null) { + finalResult = new BrowserSwitchFinalResult.Success( + result.resultUri, + pendingAuthTabRequest + ); + } else { + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + break; + default: + finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; + } + authTabCallbackResult = finalResult; + pendingAuthTabRequest = null; + } + /** * Construct a client that manages browser switching with Chrome Custom Tabs fallback only. * This constructor does not initialize Auth Tab support. For Auth Tab functionality, @@ -126,11 +133,11 @@ public BrowserSwitchClient(@NonNull ActivityResultRegistry registry) { * * @param caller The ActivityResultCaller (Activity or Fragment) used to initialize the Auth Tab launcher */ - private void initializeAuthTabLauncher(@NonNull ActivityResultCaller caller) { - authTabLauncher = AuthTabIntent.registerActivityResultLauncher( - caller, - authTabCallback - ); + private void initializeAuthTabLauncher(@NonNull ActivityResultCaller caller) { + authTabLauncher = AuthTabIntent.registerActivityResultLauncher( + caller, + authTabCallback + ); } private void initializeAuthTabLauncher(@NonNull ActivityResultRegistry registry) { @@ -307,6 +314,10 @@ private boolean isValidRequestCode(int requestCode) { * @return a {@link BrowserSwitchFinalResult} */ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull String pendingRequest) { + if (authTabResult != null) { + onAuthTabResult(authTabResult); + authTabResult = null; + } if (authTabCallbackResult != null) { BrowserSwitchFinalResult result = authTabCallbackResult; authTabCallbackResult = null; diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt index 002639d6..f440a2e7 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt @@ -47,7 +47,7 @@ fun MainContent() { val context = LocalContext.current val activity = context.findActivity() - LaunchedEffect(true) { + LifecycleEventEffect(Lifecycle.Event.ON_CREATE) { PendingRequestStore.get(context)?.let { pendingRequest -> try { browserSwitchClient.restorePendingRequest(pendingRequest) From 7357f493bf73912c1d14207002b117f1c0a1db45 Mon Sep 17 00:00:00 2001 From: saperi Date: Wed, 11 Feb 2026 21:19:32 -0800 Subject: [PATCH 06/12] linter --- .../com/braintreepayments/api/browserswitch/demo/MainContent.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt index f440a2e7..6f334e21 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt @@ -14,7 +14,6 @@ import androidx.compose.foundation.layout.safeGesturesPadding import androidx.compose.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color From 819ba3bf7377d6ca3cfb321af6941197a46676cb Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 23 Feb 2026 13:50:31 -0800 Subject: [PATCH 07/12] linter --- .../braintreepayments/api/browserswitch/demo/MainContent.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt index 6f334e21..6334485b 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt @@ -40,7 +40,8 @@ private const val RETURN_URL_SCHEME = "my-custom-url-scheme-standard" @Composable fun MainContent() { val viewModel: BrowserSwitchViewModel = viewModel { BrowserSwitchViewModel() } - val browserSwitchClient: BrowserSwitchClient = LocalActivityResultRegistryOwner.current?.let { BrowserSwitchClient(it.activityResultRegistry) } + val browserSwitchClient: BrowserSwitchClient = + LocalActivityResultRegistryOwner.current?.let { BrowserSwitchClient(it.activityResultRegistry) } ?: BrowserSwitchClient() val context = LocalContext.current From 2d296d8490a46bb4164a646028ce86a7d08a1f1f Mon Sep 17 00:00:00 2001 From: saperi Date: Tue, 24 Feb 2026 10:57:20 -0800 Subject: [PATCH 08/12] fix a problem with subsequent session not launching as intended --- .../com/braintreepayments/api/browserswitch/demo/MainContent.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt index 6334485b..6f13f272 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainContent.kt @@ -73,7 +73,6 @@ fun MainContent() { handleBrowserSwitchResult(viewModel, completeRequestResult) PendingRequestStore.clear(context) intent.data = null - browserSwitchClient.cleanup() } onPauseOrDispose { lifecycle } From 80af985f56f48a4552f5993a572959613d04fcb8 Mon Sep 17 00:00:00 2001 From: saperi Date: Wed, 25 Feb 2026 16:36:32 -0800 Subject: [PATCH 09/12] Add docs and another constructor for testing --- .../api/BrowserSwitchClient.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index 6aa1e391..59f32fa6 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -107,6 +107,33 @@ public BrowserSwitchClient(@NonNull ActivityResultCaller caller) { initializeAuthTabLauncher(caller); } + /** + * Construct a client that manages the logic for browser switching and automatically + * initializes the Auth Tab launcher. Use this constructor for flows where {@link ActivityResultCaller} is not + * available. + * + *

IMPORTANT: This constructor enables the AuthTab functionality, which has several caveats: + * + *

    + *
  • {@link LaunchType#ACTIVITY_NEW_TASK} is not supported when using AuthTab and will be ignored. + * Only {@link LaunchType#ACTIVITY_CLEAR_TOP} is supported with AuthTab. + *
  • When using SingleTop activities, you must check for launcher results in {@code onResume()} as well + * as in {@code onNewIntent()}, since the AuthTab activity result might be delivered during the + * resuming phase. + *
  • Care must be taken to avoid calling {@link #completeRequest(Intent, String)} multiple times + * for the same result. Merchants should properly track their pending request state to ensure + * the completeRequest method is only called once per browser switch session. + *
  • AuthTab support is browser version dependent. It requires Chrome version 137 + * or higher on the user's device. On devices with older browser versions, the library will + * automatically fall back to Custom Tabs. This means that enabling AuthTab is not guaranteed + * to use the AuthTab flow if the user's browser version is too old. + *
+ * + *

Consider using the default constructor {@link #BrowserSwitchClient()} if these limitations + * are incompatible with your implementation. + * + * @param registry The ActivityResultRegistry used to initialize the Auth Tab launcher + */ public BrowserSwitchClient(@NonNull ActivityResultRegistry registry) { this(new BrowserSwitchInspector(), new AuthTabInternalClient()); initializeAuthTabLauncher(registry); @@ -127,6 +154,14 @@ public BrowserSwitchClient(@NonNull ActivityResultRegistry registry) { initializeAuthTabLauncher(caller); } + @VisibleForTesting + BrowserSwitchClient(@NonNull ActivityResultRegistry registry, + BrowserSwitchInspector browserSwitchInspector, + AuthTabInternalClient authTabInternalClient) { + this(browserSwitchInspector, authTabInternalClient); + initializeAuthTabLauncher(registry); + } + /** * Initialize the Auth Tab launcher. This should be called in the activity/fragment's onCreate() * before it is started. From 2e2e7c658fd4f332f0caf8a7d2fc90da53201f58 Mon Sep 17 00:00:00 2001 From: saperi Date: Wed, 25 Feb 2026 16:50:40 -0800 Subject: [PATCH 10/12] add test --- .../api/BrowserSwitchClientUnitTest.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java index 21a38d05..238a72f2 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -26,6 +26,7 @@ import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.ActivityResultRegistry; import androidx.browser.auth.AuthTabIntent; import org.json.JSONException; @@ -283,6 +284,18 @@ public void initializeAuthTabLauncher_registersLauncherWithActivity() { } } + @Test + public void initializeAuthTabLauncher_withActivityResultRegistry_callsRegister() { + ActivityResultRegistry registry = mock(ActivityResultRegistry.class); + BrowserSwitchClient sut = new BrowserSwitchClient(registry, browserSwitchInspector, authTabInternalClient); + + verify(registry).register( + eq(sut.registryKey), + any(AuthTabIntent.AuthenticateUserResultContract.class), + eq(sut.authTabCallback) + ); + } + @Test public void start_withAuthTabLauncherInitialized_usesPendingAuthTabRequest() throws BrowserSwitchException { try (MockedStatic mockedAuthTab = mockStatic(AuthTabIntent.class)) { From 01a1f51add27e9d0cbd106af3c0572f81cb2f869 Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 2 Mar 2026 08:48:12 -0800 Subject: [PATCH 11/12] rename method --- .../java/com/braintreepayments/api/BrowserSwitchClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index 59f32fa6..ea95f93b 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -373,7 +373,7 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull return BrowserSwitchFinalResult.NoResult.INSTANCE; } - public void cleanup() { + public void cleanUp() { authTabLauncher.unregister(); } From cc06983eaf36dd89cb80d4ee759f6ccf8c44340e Mon Sep 17 00:00:00 2001 From: saperi Date: Mon, 2 Mar 2026 08:52:32 -0800 Subject: [PATCH 12/12] remove cleanup method' --- .../java/com/braintreepayments/api/BrowserSwitchClient.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java index ea95f93b..e11db2c4 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -373,10 +373,6 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull return BrowserSwitchFinalResult.NoResult.INSTANCE; } - public void cleanUp() { - authTabLauncher.unregister(); - } - /** * Checks if Auth Tab is supported on this device and if the launcher has been initialized. * @param context The application context