66import android .net .Uri ;
77
88import androidx .activity .ComponentActivity ;
9+ import androidx .activity .result .ActivityResultLauncher ;
910import androidx .annotation .NonNull ;
1011import androidx .annotation .VisibleForTesting ;
12+ import androidx .browser .auth .AuthTabIntent ;
1113
1214import com .braintreepayments .api .browserswitch .R ;
1315
1921public class BrowserSwitchClient {
2022
2123 private final BrowserSwitchInspector browserSwitchInspector ;
22-
23- private final ChromeCustomTabsInternalClient customTabsInternalClient ;
24+ private final AuthTabInternalClient authTabInternalClient ;
25+ private ActivityResultLauncher <Intent > authTabLauncher ;
26+ private BrowserSwitchRequest pendingAuthTabRequest ;
2427
2528 /**
2629 * Construct a client that manages the logic for browser switching.
2730 */
2831 public BrowserSwitchClient () {
29- this (new BrowserSwitchInspector (), new ChromeCustomTabsInternalClient ());
32+ this (new BrowserSwitchInspector (), new AuthTabInternalClient ());
3033 }
3134
3235 @ VisibleForTesting
3336 BrowserSwitchClient (BrowserSwitchInspector browserSwitchInspector ,
34- ChromeCustomTabsInternalClient customTabsInternalClient ) {
37+ AuthTabInternalClient authTabInternalClient ) {
3538 this .browserSwitchInspector = browserSwitchInspector ;
36- this .customTabsInternalClient = customTabsInternalClient ;
39+ this .authTabInternalClient = authTabInternalClient ;
40+ }
41+
42+ /**
43+ * Initialize the Auth Tab launcher. This should be called in the activity's onCreate()
44+ * before the activity is started.
45+ */
46+ public void initializeAuthTabLauncher (@ NonNull ComponentActivity activity ,
47+ @ NonNull AuthTabCallback callback ) {
48+ this .authTabLauncher = AuthTabIntent .registerActivityResultLauncher (
49+ activity ,
50+ result -> {
51+ BrowserSwitchFinalResult finalResult ;
52+
53+ switch (result .resultCode ) {
54+ case AuthTabIntent .RESULT_OK :
55+ if (result .resultUri != null && pendingAuthTabRequest != null ) {
56+ finalResult = new BrowserSwitchFinalResult .Success (
57+ result .resultUri ,
58+ pendingAuthTabRequest
59+ );
60+ } else {
61+ finalResult = BrowserSwitchFinalResult .NoResult .INSTANCE ;
62+ }
63+ break ;
64+ case AuthTabIntent .RESULT_CANCELED :
65+ finalResult = BrowserSwitchFinalResult .NoResult .INSTANCE ;
66+ break ;
67+ case AuthTabIntent .RESULT_VERIFICATION_FAILED :
68+ finalResult = BrowserSwitchFinalResult .NoResult .INSTANCE ;
69+ break ;
70+ case AuthTabIntent .RESULT_VERIFICATION_TIMED_OUT :
71+ finalResult = BrowserSwitchFinalResult .NoResult .INSTANCE ;
72+ break ;
73+ default :
74+ finalResult = BrowserSwitchFinalResult .NoResult .INSTANCE ;
75+ }
76+ callback .onResult (finalResult );
77+ pendingAuthTabRequest = null ;
78+ }
79+ );
3780 }
3881
3982 /**
40- * Open a browser or <a href="https://developer.chrome.com/multidevice/android/customtabs">Chrome Custom Tab</a>
41- * with a given set of {@link BrowserSwitchOptions} from an Android activity.
83+ * Open a browser or Auth Tab with a given set of {@link BrowserSwitchOptions} from an Android activity.
4284 *
4385 * @param activity the activity used to start browser switch
4486 * @param browserSwitchOptions {@link BrowserSwitchOptions} the options used to configure the browser switch
4587 * @return a {@link BrowserSwitchStartResult.Started} that should be stored and passed to
46- * {@link BrowserSwitchClient#completeRequest(Intent, String)} upon return to the app,
88+ * {@link BrowserSwitchClient#completeRequest(Intent, String)} upon return to the app (for Custom Tabs fallback) ,
4789 * or {@link BrowserSwitchStartResult.Failure} if browser could not be launched.
4890 */
4991 @ NonNull
50- public BrowserSwitchStartResult start (@ NonNull ComponentActivity activity , @ NonNull BrowserSwitchOptions browserSwitchOptions ) {
92+ public BrowserSwitchStartResult start (@ NonNull ComponentActivity activity ,
93+ @ NonNull BrowserSwitchOptions browserSwitchOptions ) {
5194 try {
5295 assertCanPerformBrowserSwitch (activity , browserSwitchOptions );
5396 } catch (BrowserSwitchException e ) {
@@ -58,29 +101,53 @@ public BrowserSwitchStartResult start(@NonNull ComponentActivity activity, @NonN
58101 int requestCode = browserSwitchOptions .getRequestCode ();
59102 String returnUrlScheme = browserSwitchOptions .getReturnUrlScheme ();
60103 Uri appLinkUri = browserSwitchOptions .getAppLinkUri ();
61-
62104 JSONObject metadata = browserSwitchOptions .getMetadata ();
63105
64106 if (activity .isFinishing ()) {
65107 String activityFinishingMessage =
66108 "Unable to start browser switch while host Activity is finishing." ;
67109 return new BrowserSwitchStartResult .Failure (new BrowserSwitchException (activityFinishingMessage ));
68- } else {
69- LaunchType launchType = browserSwitchOptions .getLaunchType ();
70- BrowserSwitchRequest request ;
71- try {
72- request = new BrowserSwitchRequest (
73- requestCode ,
74- browserSwitchUrl ,
75- metadata ,
76- returnUrlScheme ,
77- appLinkUri
78- );
79- customTabsInternalClient .launchUrl (activity , browserSwitchUrl , launchType );
80- return new BrowserSwitchStartResult .Started (request .toBase64EncodedJSON ());
81- } catch (ActivityNotFoundException | BrowserSwitchException e ) {
82- return new BrowserSwitchStartResult .Failure (new BrowserSwitchException ("Unable to start browser switch without a web browser." , e ));
110+ }
111+
112+ LaunchType launchType = browserSwitchOptions .getLaunchType ();
113+ BrowserSwitchRequest request ;
114+
115+ try {
116+ request = new BrowserSwitchRequest (
117+ requestCode ,
118+ browserSwitchUrl ,
119+ metadata ,
120+ returnUrlScheme ,
121+ appLinkUri
122+ );
123+
124+ boolean useAuthTab = authTabInternalClient .isAuthTabSupported (activity );
125+
126+ if (useAuthTab ) {
127+ this .pendingAuthTabRequest = request ;
83128 }
129+
130+ authTabInternalClient .launchUrl (
131+ activity ,
132+ browserSwitchUrl ,
133+ returnUrlScheme ,
134+ appLinkUri ,
135+ authTabLauncher ,
136+ launchType
137+ );
138+
139+ return new BrowserSwitchStartResult .Started (request .toBase64EncodedJSON ());
140+
141+ } catch (ActivityNotFoundException e ) {
142+ this .pendingAuthTabRequest = null ;
143+ return new BrowserSwitchStartResult .Failure (
144+ new BrowserSwitchException ("Unable to start browser switch without a web browser." , e )
145+ );
146+ } catch (Exception e ) {
147+ this .pendingAuthTabRequest = null ;
148+ return new BrowserSwitchStartResult .Failure (
149+ new BrowserSwitchException ("Unable to start browser switch: " + e .getMessage (), e )
150+ );
84151 }
85152 }
86153
@@ -121,20 +188,18 @@ private boolean isValidRequestCode(int requestCode) {
121188 }
122189
123190 /**
124- * Completes the browser switch flow and returns a browser switch result if a match is found for
125- * the given {@link BrowserSwitchRequest}
191+ * Completes the browser switch flow for Custom Tabs fallback scenarios.
192+ * This method is still needed for devices that don't support Auth Tab.
193+ *
194+ * <p>See <a href="https://developer.chrome.com/docs/android/custom-tabs/guide-auth-tab#fallback_to_custom_tabs">
195+ * Auth Tab Fallback Documentation</a> for details on when Custom Tabs fallback is required
126196 *
127- * @param intent the intent to return to your application containing a deep link result from the
128- * browser flow
129- * @param pendingRequest the pending request string returned from {@link BrowserSwitchStartResult.Started} via
130- * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)}
131- * @return a {@link BrowserSwitchFinalResult.Success} if the browser switch was successfully
132- * completed, or {@link BrowserSwitchFinalResult.NoResult} if no result can be found for the given
133- * pending request String. A {@link BrowserSwitchFinalResult.NoResult} will be
134- * returned if the user returns to the app without completing the browser switch flow.
197+ * @param intent the intent to return to your application containing a deep link result
198+ * @param pendingRequest the pending request string returned from {@link BrowserSwitchStartResult.Started}
199+ * @return a {@link BrowserSwitchFinalResult}
135200 */
136201 public BrowserSwitchFinalResult completeRequest (@ NonNull Intent intent , @ NonNull String pendingRequest ) {
137- if (intent != null && intent .getData () != null ) {
202+ if (intent .getData () != null ) {
138203 Uri returnUrl = intent .getData ();
139204
140205 try {
@@ -149,4 +214,8 @@ public BrowserSwitchFinalResult completeRequest(@NonNull Intent intent, @NonNull
149214 }
150215 return BrowserSwitchFinalResult .NoResult .INSTANCE ;
151216 }
152- }
217+
218+ public boolean isAuthTabSupported (Context context ) {
219+ return authTabInternalClient .isAuthTabSupported (context );
220+ }
221+ }
0 commit comments