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 c7e93a6b..ea4bcafd 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/AuthTabInternalClient.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/AuthTabInternalClient.kt @@ -31,12 +31,12 @@ internal class AuthTabInternalClient ( url: Uri, returnUrlScheme: String?, appLinkUri: Uri?, - launcher: ActivityResultLauncher, + launcher: ActivityResultLauncher?, launchType: LaunchType? ) { val useAuthTab = isAuthTabSupported(context) - if (useAuthTab) { + if (useAuthTab && launcher != null) { val authTabIntent = authTabIntentBuilder.build() if (launchType == LaunchType.ACTIVITY_CLEAR_TOP) { 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 b275d88a..d7e33763 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -1,13 +1,16 @@ package com.braintreepayments.api; +import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; import androidx.activity.ComponentActivity; +import androidx.activity.result.ActivityResultCaller; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import androidx.browser.auth.AuthTabIntent; @@ -25,31 +28,78 @@ public class BrowserSwitchClient { private ActivityResultLauncher authTabLauncher; private BrowserSwitchRequest pendingAuthTabRequest; + @Nullable + private BrowserSwitchFinalResult authTabCallbackResult; + /** - * Construct a client that manages the logic for browser switching. + * 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, + * use {@link #BrowserSwitchClient(Activity)} instead. */ public BrowserSwitchClient() { this(new BrowserSwitchInspector(), new AuthTabInternalClient()); } + /** + * Construct a client that manages the logic for browser switching and automatically + * initializes the Auth Tab launcher. + * + *

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

    + *
  • The provided activity MUST implement {@link ActivityResultCaller}, which is true for all + * instances of {@link androidx.activity.ComponentActivity}. + *
  • {@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 activity The activity used to initialize the Auth Tab launcher. Must implement + * {@link ActivityResultCaller}. + */ + public BrowserSwitchClient(@NonNull Activity activity) { + this(new BrowserSwitchInspector(), new AuthTabInternalClient()); + initializeAuthTabLauncher(activity); + } + @VisibleForTesting BrowserSwitchClient(BrowserSwitchInspector browserSwitchInspector, AuthTabInternalClient authTabInternalClient) { this.browserSwitchInspector = browserSwitchInspector; this.authTabInternalClient = authTabInternalClient; + this.authTabCallbackResult = null; } /** * Initialize the Auth Tab launcher. This should be called in the activity's onCreate() * before the activity is started. + * + * @param activity The activity used to initialize the Auth Tab launcher */ - public void initializeAuthTabLauncher(@NonNull ComponentActivity activity, - @NonNull AuthTabCallback callback) { + public void initializeAuthTabLauncher(@NonNull Activity activity) { + + if (!(activity instanceof ActivityResultCaller)) { + return; + } + + ComponentActivity componentActivity = (ComponentActivity) activity; + this.authTabLauncher = AuthTabIntent.registerActivityResultLauncher( - activity, + componentActivity, result -> { BrowserSwitchFinalResult finalResult; - switch (result.resultCode) { case AuthTabIntent.RESULT_OK: if (result.resultUri != null && pendingAuthTabRequest != null) { @@ -61,19 +111,10 @@ public void initializeAuthTabLauncher(@NonNull ComponentActivity activity, finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; } break; - case AuthTabIntent.RESULT_CANCELED: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - break; - case AuthTabIntent.RESULT_VERIFICATION_FAILED: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - break; - case AuthTabIntent.RESULT_VERIFICATION_TIMED_OUT: - finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; - break; default: finalResult = BrowserSwitchFinalResult.NoResult.INSTANCE; } - callback.onResult(finalResult); + this.authTabCallbackResult = finalResult; pendingAuthTabRequest = null; } ); @@ -91,6 +132,9 @@ public void initializeAuthTabLauncher(@NonNull ComponentActivity activity, @NonNull public BrowserSwitchStartResult start(@NonNull ComponentActivity activity, @NonNull BrowserSwitchOptions browserSwitchOptions) { + + this.authTabCallbackResult = null; + try { assertCanPerformBrowserSwitch(activity, browserSwitchOptions); } catch (BrowserSwitchException e) { @@ -121,7 +165,7 @@ public BrowserSwitchStartResult start(@NonNull ComponentActivity activity, appLinkUri ); - boolean useAuthTab = authTabInternalClient.isAuthTabSupported(activity); + boolean useAuthTab = isAuthTabSupported(activity); if (useAuthTab) { this.pendingAuthTabRequest = request; @@ -188,18 +232,27 @@ private boolean isValidRequestCode(int requestCode) { } /** - * Completes the browser switch flow for Custom Tabs fallback scenarios. - * This method is still needed for devices that don't support Auth Tab. + * Completes the browser switch flow for both Auth Tab and Custom Tabs fallback scenarios. + * This method first checks if we have a result from the Auth Tab callback, + * and returns it if available. Otherwise, it follows the Custom Tabs flow. * *

See * Auth Tab Fallback Documentation for details on when Custom Tabs fallback is required * + *

IMPORTANT: When using Auth Tab with SingleTop activities, you must call this method + * in both {@code onNewIntent()} and {@code onResume()} to ensure the result is properly processed + * regardless of which launch mode is used. + * * @param intent the intent to return to your application containing a deep link result * @param pendingRequest the pending request string returned from {@link BrowserSwitchStartResult.Started} * @return a {@link BrowserSwitchFinalResult} */ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull String pendingRequest) { - if (intent.getData() != null) { + if (authTabCallbackResult != null) { + BrowserSwitchFinalResult result = authTabCallbackResult; + authTabCallbackResult = null; + return result; + } else if (intent.getData() != null) { Uri returnUrl = intent.getData(); try { @@ -215,7 +268,14 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull return BrowserSwitchFinalResult.NoResult.INSTANCE; } - public boolean isAuthTabSupported(Context context) { - return authTabInternalClient.isAuthTabSupported(context); + /** + * Checks if Auth Tab is supported on this device and if the launcher has been initialized. + * @param context The application context + * @return true if Auth Tab is supported by the browser AND the launcher has been initialized, + * false otherwise + */ + @VisibleForTesting + boolean isAuthTabSupported(Context context) { + return authTabLauncher != null && authTabInternalClient.isAuthTabSupported(context); } } \ No newline at end of file diff --git a/browser-switch/src/test/java/com/braintreepayments/api/AuthTabInternalClientUnitTest.kt b/browser-switch/src/test/java/com/braintreepayments/api/AuthTabInternalClientUnitTest.kt index b1635664..59a7ef86 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/AuthTabInternalClientUnitTest.kt +++ b/browser-switch/src/test/java/com/braintreepayments/api/AuthTabInternalClientUnitTest.kt @@ -157,7 +157,6 @@ class AuthTabInternalClientUnitTest { } } - @Test fun `launchUrl handles app link with no path`() { val appLinkUri = Uri.parse("https://example.com") @@ -216,4 +215,20 @@ class AuthTabInternalClientUnitTest { assertTrue(intent.flags and Intent.FLAG_ACTIVITY_CLEAR_TOP != 0) } -} \ No newline at end of file + + @Test + fun `launchUrl with null launcher falls back to Custom Tabs even when Auth Tab is supported`() { + val packageName = "com.android.chrome" + every { CustomTabsClient.getPackageName(context, null) } returns packageName + every { CustomTabsClient.isAuthTabSupported(context, packageName) } returns true + + val client = AuthTabInternalClient(authTabBuilder, customTabsBuilder) + val returnUrlScheme = "example" + + client.launchUrl(context, url, returnUrlScheme, null, null, null) + + verify { + customTabsIntent.launchUrl(context, url) + } + } +} 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 dceadfd0..8d5eacf5 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -1,6 +1,7 @@ package com.braintreepayments.api; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -252,9 +253,8 @@ public void initializeAuthTabLauncher_registersLauncherWithActivity() { )).thenReturn(mockLauncher); BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient); - AuthTabCallback callback = mock(AuthTabCallback.class); - sut.initializeAuthTabLauncher(componentActivity, callback); + sut.initializeAuthTabLauncher(componentActivity); mockedAuthTab.verify(() -> AuthTabIntent.registerActivityResultLauncher( eq(componentActivity), @@ -290,8 +290,7 @@ public void start_withAuthTabLauncherInitialized_usesPendingAuthTabRequest() thr BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient); - AuthTabCallback callback = mock(AuthTabCallback.class); - sut.initializeAuthTabLauncher(componentActivity, callback); + sut.initializeAuthTabLauncher(componentActivity); JSONObject metadata = new JSONObject(); BrowserSwitchOptions options = new BrowserSwitchOptions() @@ -323,7 +322,7 @@ public void start_withAuthTabLauncherInitialized_usesPendingAuthTabRequest() thr } @Test - public void authTabCallback_withResultOK_callsCallbackWithSuccess() { + public void authTabCallback_withResultOK_setsInternalCallbackResult() { try (MockedStatic mockedAuthTab = mockStatic(AuthTabIntent.class)) { ArgumentCaptor> callbackCaptor = @@ -333,7 +332,6 @@ public void authTabCallback_withResultOK_callsCallbackWithSuccess() { callbackCaptor.capture() )).thenReturn(mockLauncher); - when(browserSwitchInspector.isDeviceConfiguredForDeepLinking( componentActivity.getApplicationContext(), "return-url-scheme" @@ -341,9 +339,7 @@ public void authTabCallback_withResultOK_callsCallbackWithSuccess() { when(authTabInternalClient.isAuthTabSupported(componentActivity)).thenReturn(true); BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient); - AuthTabCallback mockCallback = mock(AuthTabCallback.class); - - sut.initializeAuthTabLauncher(componentActivity, mockCallback); + sut.initializeAuthTabLauncher(componentActivity); JSONObject metadata = new JSONObject(); BrowserSwitchOptions options = new BrowserSwitchOptions() @@ -360,11 +356,8 @@ public void authTabCallback_withResultOK_callsCallbackWithSuccess() { callbackCaptor.getValue().onActivityResult(mockAuthResult); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(BrowserSwitchFinalResult.class); - verify(mockCallback).onResult(resultCaptor.capture()); - - BrowserSwitchFinalResult capturedResult = resultCaptor.getValue(); + Intent dummyIntent = new Intent(); + BrowserSwitchFinalResult capturedResult = sut.completeRequest(dummyIntent, "dummyPendingRequest"); assertTrue(capturedResult instanceof BrowserSwitchFinalResult.Success); BrowserSwitchFinalResult.Success successResult = @@ -385,9 +378,8 @@ public void authTabCallback_withResultCanceled_callsCallbackWithNoResult() { )).thenReturn(mockLauncher); BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient); - AuthTabCallback mockCallback = mock(AuthTabCallback.class); - sut.initializeAuthTabLauncher(componentActivity, mockCallback); + sut.initializeAuthTabLauncher(componentActivity); AuthTabIntent.AuthResult mockAuthResult = mock(AuthTabIntent.AuthResult.class, withSettings() .useConstructor(AuthTabIntent.RESULT_CANCELED, null) @@ -395,11 +387,8 @@ public void authTabCallback_withResultCanceled_callsCallbackWithNoResult() { callbackCaptor.getValue().onActivityResult(mockAuthResult); - ArgumentCaptor resultCaptor = - ArgumentCaptor.forClass(BrowserSwitchFinalResult.class); - verify(mockCallback).onResult(resultCaptor.capture()); - - BrowserSwitchFinalResult capturedResult = resultCaptor.getValue(); + Intent dummyIntent = new Intent(); + BrowserSwitchFinalResult capturedResult = sut.completeRequest(dummyIntent, "dummyPendingRequest"); assertTrue(capturedResult instanceof BrowserSwitchFinalResult.NoResult); } } @@ -436,7 +425,40 @@ public void start_withoutAuthTabLauncher_fallsBackToCustomTabs() { } @Test - public void isAuthTabSupported_delegatesToInternalClient() { + public void start_whenAuthTabLauncherIsNull_fallsBackToCustomTabs() { + when(browserSwitchInspector.isDeviceConfiguredForDeepLinking( + componentActivity.getApplicationContext(), + "return-url-scheme" + )).thenReturn(true); + + // Explicitly ensure AuthTab is supported but we still fallback due to null launcher + when(authTabInternalClient.isAuthTabSupported(componentActivity)).thenReturn(true); + + BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient); + + JSONObject metadata = new JSONObject(); + BrowserSwitchOptions options = new BrowserSwitchOptions() + .requestCode(123) + .url(browserSwitchDestinationUrl) + .returnUrlScheme("return-url-scheme") + .metadata(metadata); + + BrowserSwitchStartResult result = sut.start(componentActivity, options); + + assertTrue(result instanceof BrowserSwitchStartResult.Started); + + verify(authTabInternalClient).launchUrl( + eq(componentActivity), + eq(browserSwitchDestinationUrl), + eq("return-url-scheme"), + isNull(), + isNull(), + isNull() + ); + } + + @Test + public void isAuthTabSupported_returnsFalseWhenLauncherNotInitialized() { when(authTabInternalClient.isAuthTabSupported(applicationContext)).thenReturn(true); BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, @@ -444,10 +466,69 @@ public void isAuthTabSupported_delegatesToInternalClient() { boolean result = sut.isAuthTabSupported(applicationContext); - assertTrue(result); - verify(authTabInternalClient).isAuthTabSupported(applicationContext); + assertFalse(result); + } + + @Test + public void isAuthTabSupported_returnsTrueWhenLauncherInitialized() { + try (MockedStatic mockedAuthTab = mockStatic(AuthTabIntent.class)) { + when(authTabInternalClient.isAuthTabSupported(applicationContext)).thenReturn(true); + + mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher( + any(ComponentActivity.class), + any(ActivityResultCallback.class) + )).thenReturn(mockLauncher); + + BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, + authTabInternalClient); + + sut.initializeAuthTabLauncher(componentActivity); + + boolean result = sut.isAuthTabSupported(applicationContext); + + assertTrue(result); + verify(authTabInternalClient).isAuthTabSupported(applicationContext); + } + } + + @Test + public void isAuthTabSupported_returnsFalseWhenBrowserDoesNotSupportAuthTab() { + try (MockedStatic mockedAuthTab = mockStatic(AuthTabIntent.class)) { + when(authTabInternalClient.isAuthTabSupported(applicationContext)).thenReturn(false); + + mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher( + any(ComponentActivity.class), + any(ActivityResultCallback.class) + )).thenReturn(mockLauncher); + + BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, + authTabInternalClient); + + sut.initializeAuthTabLauncher(componentActivity); + + boolean result = sut.isAuthTabSupported(applicationContext); + + assertFalse(result); + verify(authTabInternalClient).isAuthTabSupported(applicationContext); + } } + @Test + public void parameterizedConstructor_initializesAuthTabLauncher() { + try (MockedStatic mockedAuthTab = mockStatic(AuthTabIntent.class)) { + mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher( + any(ComponentActivity.class), + any(ActivityResultCallback.class) + )).thenReturn(mockLauncher); + + BrowserSwitchClient sut = new BrowserSwitchClient(componentActivity); + + mockedAuthTab.verify(() -> AuthTabIntent.registerActivityResultLauncher( + eq(componentActivity), + any(ActivityResultCallback.class) + )); + } + } @Test public void completeRequest_whenAppLinkMatches_successReturnedWithAppLink() throws BrowserSwitchException, JSONException { 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 886efd89..300194f8 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 @@ -2,6 +2,7 @@ package com.braintreepayments.api.browserswitch.demo import android.net.Uri import android.os.Bundle +import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.viewModels @@ -30,19 +31,11 @@ class ComposeActivity : ComponentActivity() { private val viewModel by viewModels() private lateinit var browserSwitchClient: BrowserSwitchClient - private var useAuthTab = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - browserSwitchClient = BrowserSwitchClient() - browserSwitchClient.initializeAuthTabLauncher(this) { result -> - handleBrowserSwitchResult(result) - } - - if (browserSwitchClient.isAuthTabSupported(this)) { - useAuthTab = true - } - + // Initialize BrowserSwitchClient with the parameterized constructor + browserSwitchClient = BrowserSwitchClient(this) setContent { Column(modifier = Modifier.safeGesturesPadding()) { BrowserSwitchButton { @@ -55,14 +48,11 @@ class ComposeActivity : ComponentActivity() { override fun onResume() { super.onResume() - // Only handle Custom Tabs fall back case - if (!useAuthTab) { - 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 } } @@ -90,10 +80,7 @@ class ComposeActivity : ComponentActivity() { when (val startResult = browserSwitchClient.start(this, browserSwitchOptions)) { is BrowserSwitchStartResult.Started -> { - // Only store for Custom Tabs fall back - if (!useAuthTab) { PendingRequestStore.put(this, startResult.pendingRequest) - } } is BrowserSwitchStartResult.Failure -> viewModel.browserSwitchError = startResult.error diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/DemoActivitySingleTop.java index 734931e3..1589697e 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/DemoActivitySingleTop.java @@ -31,22 +31,11 @@ public class DemoActivitySingleTop extends AppCompatActivity { @VisibleForTesting BrowserSwitchClient browserSwitchClient = null; - private boolean useAuthTab = false; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); - browserSwitchClient = new BrowserSwitchClient(); - browserSwitchClient.initializeAuthTabLauncher(this, this::handleBrowserSwitchResult); - - if (browserSwitchClient.isAuthTabSupported(this)) { - useAuthTab = true; - // Show a toast to indicate Auth Tab is being used - Toast.makeText(this, "Using Auth Tab", Toast.LENGTH_SHORT).show(); - } else { - Toast.makeText(this, "Using Custom Tabs (Auth Tab not supported)", - Toast.LENGTH_SHORT).show(); - } + // Initialize BrowserSwitchClient with the parameterized constructor + browserSwitchClient = new BrowserSwitchClient(this); FragmentManager fm = getSupportFragmentManager(); if (getDemoFragment() == null) { @@ -71,28 +60,31 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - - // Only handle Custom Tabs fallback case - if (!useAuthTab) { - String pendingRequest = PendingRequestStore.get(this); - if (pendingRequest != null) { - BrowserSwitchFinalResult result = - browserSwitchClient.completeRequest(intent, pendingRequest); - handleBrowserSwitchResult(result); - PendingRequestStore.clear(this); - intent.setData(null); - } + String pendingRequest = PendingRequestStore.get(this); + if (pendingRequest != null) { + BrowserSwitchFinalResult result = + browserSwitchClient.completeRequest(intent, pendingRequest); + handleBrowserSwitchResult(result); + PendingRequestStore.clear(this); + intent.setData(null); } } @Override protected void onResume() { super.onResume(); + String pendingRequest = PendingRequestStore.get(this); + if (pendingRequest != null) { + // When using AuthTab, results come via the ActivityResultLauncher callback, + // so we need to check for results in onResume too, not just onNewIntent + BrowserSwitchFinalResult result = browserSwitchClient.completeRequest(getIntent(), pendingRequest); - // Only check for incomplete browser switch in Custom Tabs mode - if (!useAuthTab) { - String pendingRequest = PendingRequestStore.get(this); - if (pendingRequest != null) { + if (result instanceof BrowserSwitchFinalResult.Success) { + handleBrowserSwitchResult(result); + PendingRequestStore.clear(this); + getIntent().setData(null); + } + else { Objects.requireNonNull(getDemoFragment()) .onBrowserSwitchError(new Exception("User did not complete browser switch")); PendingRequestStore.clear(this); @@ -117,11 +109,8 @@ private void handleBrowserSwitchResult(BrowserSwitchFinalResult result) { public void startBrowserSwitch(BrowserSwitchOptions options) throws BrowserSwitchException { BrowserSwitchStartResult result = browserSwitchClient.start(this, options); if (result instanceof BrowserSwitchStartResult.Started) { - // Only store pending request for Custom Tabs fallback - if (!useAuthTab) { - PendingRequestStore.put(this, - ((BrowserSwitchStartResult.Started) result).getPendingRequest()); - } + PendingRequestStore.put(this, + ((BrowserSwitchStartResult.Started) result).getPendingRequest()); } else if (result instanceof BrowserSwitchStartResult.Failure) { Objects.requireNonNull(getDemoFragment()) .onBrowserSwitchError(((BrowserSwitchStartResult.Failure) result).getError()); diff --git a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainActivity.java b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainActivity.java index a5713960..56de514e 100644 --- a/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainActivity.java +++ b/demo/src/main/java/com/braintreepayments/api/browserswitch/demo/MainActivity.java @@ -26,15 +26,6 @@ protected void onCreate(Bundle savedInstanceState) { Button singleTopButton = findViewById(R.id.single_top_button); singleTopButton.setOnClickListener(this::launchSingleTopBrowserSwitch); - - // Show Auth Tab support status via Toast - BrowserSwitchClient client = new BrowserSwitchClient(); - if (client.isAuthTabSupported(this)) { - Toast.makeText(this, "Auth Tab is supported", Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(this, "Using Custom Tabs fallback", Toast.LENGTH_LONG).show(); - } - // Support Edge-to-Edge layout in Android 15 // Ref: https://developer.android.com/develop/ui/views/layout/edge-to-edge#cutout-insets View navHostView = findViewById(R.id.content);