Skip to content

Commit 4673301

Browse files
authored
Update CHANGELOG.md and an argument for a parametrized constructor (#123)
* updated default constructor to accept `ActivityResultCaller` as a parameter updated `CHANGELOG.md` * addressed PR suggestions * addressed PR suggestions: switch the version to `unreleased`
1 parent 6ddac0d commit 4673301

File tree

4 files changed

+83
-80
lines changed

4 files changed

+83
-80
lines changed

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# browser-switch-android Release Notes
22

3+
## unreleased
4+
5+
* Add AuthTab Support
6+
* Upgrade `androidx.browser:browser` dependency version to 1.9.0
7+
* Upgrade `compileSdkVersion` and `targetSdkVersion` to API 36
8+
* Replace `ChromeCustomTabsInternalClient.java` with `AuthTabInternalClient.kt`
9+
* Add parameterized constructor `BrowserSwitchClient(ActivityResultCaller)` to initialize AuthTab support
10+
* Maintain default constructor `BrowserSwitchClient()` (without AuthTab support) for backward compatibility
11+
312
## 3.1.0
413

514
* Add `LaunchType` to `BrowserSwitchOptions` to specify how the browser switch should be launched

browser-switch/src/main/java/com/braintreepayments/api/AuthTabCallback.kt

Lines changed: 0 additions & 11 deletions
This file was deleted.

browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.braintreepayments.api;
22

3-
import android.app.Activity;
43
import android.content.ActivityNotFoundException;
54
import android.content.Context;
65
import android.content.Intent;
@@ -34,7 +33,7 @@ public class BrowserSwitchClient {
3433
/**
3534
* Construct a client that manages browser switching with Chrome Custom Tabs fallback only.
3635
* This constructor does not initialize Auth Tab support. For Auth Tab functionality,
37-
* use {@link #BrowserSwitchClient(Activity)} instead.
36+
* use {@link #BrowserSwitchClient(ActivityResultCaller)} instead.
3837
*/
3938
public BrowserSwitchClient() {
4039
this(new BrowserSwitchInspector(), new AuthTabInternalClient());
@@ -47,8 +46,9 @@ public BrowserSwitchClient() {
4746
* <p>IMPORTANT: This constructor enables the AuthTab functionality, which has several caveats:
4847
*
4948
* <ul>
50-
* <li>The provided activity MUST implement {@link ActivityResultCaller}, which is true for all
51-
* instances of {@link androidx.activity.ComponentActivity}.
49+
* <li><strong>This constructor must be called in the activity/fragment's {@code onCreate()} method</strong>
50+
* to properly register the activity result launcher before the activity/fragment is started.
51+
* <li>The caller must be an {@link ActivityResultCaller} to register for activity results.
5252
* <li>{@link LaunchType#ACTIVITY_NEW_TASK} is not supported when using AuthTab and will be ignored.
5353
* Only {@link LaunchType#ACTIVITY_CLEAR_TOP} is supported with AuthTab.
5454
* <li>When using SingleTop activities, you must check for launcher results in {@code onResume()} as well
@@ -66,38 +66,38 @@ public BrowserSwitchClient() {
6666
* <p>Consider using the default constructor {@link #BrowserSwitchClient()} if these limitations
6767
* are incompatible with your implementation.
6868
*
69-
* @param activity The activity used to initialize the Auth Tab launcher. Must implement
70-
* {@link ActivityResultCaller}.
69+
* @param caller The ActivityResultCaller used to initialize the Auth Tab launcher.
7170
*/
72-
public BrowserSwitchClient(@NonNull Activity activity) {
71+
public BrowserSwitchClient(@NonNull ActivityResultCaller caller) {
7372
this(new BrowserSwitchInspector(), new AuthTabInternalClient());
74-
initializeAuthTabLauncher(activity);
73+
initializeAuthTabLauncher(caller);
7574
}
7675

7776
@VisibleForTesting
7877
BrowserSwitchClient(BrowserSwitchInspector browserSwitchInspector,
7978
AuthTabInternalClient authTabInternalClient) {
8079
this.browserSwitchInspector = browserSwitchInspector;
8180
this.authTabInternalClient = authTabInternalClient;
82-
this.authTabCallbackResult = null;
81+
}
82+
83+
@VisibleForTesting
84+
BrowserSwitchClient(@NonNull ActivityResultCaller caller,
85+
BrowserSwitchInspector inspector,
86+
AuthTabInternalClient internal) {
87+
this(inspector, internal);
88+
initializeAuthTabLauncher(caller);
8389
}
8490

8591
/**
86-
* Initialize the Auth Tab launcher. This should be called in the activity's onCreate()
87-
* before the activity is started.
92+
* Initialize the Auth Tab launcher. This should be called in the activity/fragment's onCreate()
93+
* before it is started.
8894
*
89-
* @param activity The activity used to initialize the Auth Tab launcher
95+
* @param caller The ActivityResultCaller (Activity or Fragment) used to initialize the Auth Tab launcher
9096
*/
91-
public void initializeAuthTabLauncher(@NonNull Activity activity) {
92-
93-
if (!(activity instanceof ActivityResultCaller)) {
94-
return;
95-
}
96-
97-
ComponentActivity componentActivity = (ComponentActivity) activity;
97+
private void initializeAuthTabLauncher(@NonNull ActivityResultCaller caller) {
9898

9999
this.authTabLauncher = AuthTabIntent.registerActivityResultLauncher(
100-
componentActivity,
100+
caller,
101101
result -> {
102102
BrowserSwitchFinalResult finalResult;
103103
switch (result.resultCode) {

browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import androidx.activity.ComponentActivity;
2525
import androidx.activity.result.ActivityResultCallback;
26+
import androidx.activity.result.ActivityResultCaller;
2627
import androidx.activity.result.ActivityResultLauncher;
2728
import androidx.browser.auth.AuthTabIntent;
2829

@@ -252,9 +253,11 @@ public void initializeAuthTabLauncher_registersLauncherWithActivity() {
252253
any(ActivityResultCallback.class)
253254
)).thenReturn(mockLauncher);
254255

255-
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient);
256-
257-
sut.initializeAuthTabLauncher(componentActivity);
256+
BrowserSwitchClient sut = new BrowserSwitchClient(
257+
componentActivity,
258+
browserSwitchInspector,
259+
authTabInternalClient
260+
);
258261

259262
mockedAuthTab.verify(() -> AuthTabIntent.registerActivityResultLauncher(
260263
eq(componentActivity),
@@ -280,27 +283,30 @@ public void start_withAuthTabLauncherInitialized_usesPendingAuthTabRequest() thr
280283
"return-url-scheme"
281284
)).thenReturn(true);
282285
when(authTabInternalClient.isAuthTabSupported(componentActivity)).thenReturn(true);
283-
284-
ArgumentCaptor<ActivityResultCallback> callbackCaptor =
286+
ArgumentCaptor<ActivityResultCallback<AuthTabIntent.AuthResult>> callbackCaptor =
285287
ArgumentCaptor.forClass(ActivityResultCallback.class);
286288
mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher(
287289
eq(componentActivity),
288290
callbackCaptor.capture()
289291
)).thenReturn(mockLauncher);
292+
BrowserSwitchClient sut = new BrowserSwitchClient(
293+
componentActivity,
294+
browserSwitchInspector,
295+
authTabInternalClient
296+
);
290297

291-
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient);
292-
293-
sut.initializeAuthTabLauncher(componentActivity);
298+
mockedAuthTab.verify(() -> AuthTabIntent.registerActivityResultLauncher(
299+
eq(componentActivity),
300+
any(ActivityResultCallback.class)
301+
));
294302

295303
JSONObject metadata = new JSONObject();
296304
BrowserSwitchOptions options = new BrowserSwitchOptions()
297305
.requestCode(123)
298306
.url(browserSwitchDestinationUrl)
299307
.returnUrlScheme("return-url-scheme")
300308
.metadata(metadata);
301-
302309
BrowserSwitchStartResult result = sut.start(componentActivity, options);
303-
304310
assertTrue(result instanceof BrowserSwitchStartResult.Started);
305311

306312
verify(authTabInternalClient).launchUrl(
@@ -314,10 +320,6 @@ public void start_withAuthTabLauncherInitialized_usesPendingAuthTabRequest() thr
314320

315321
String pendingRequestString = ((BrowserSwitchStartResult.Started) result).getPendingRequest();
316322
assertNotNull(pendingRequestString);
317-
318-
BrowserSwitchRequest decodedRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestString);
319-
assertEquals(123, decodedRequest.getRequestCode());
320-
assertEquals(browserSwitchDestinationUrl, decodedRequest.getUrl());
321323
}
322324
}
323325

@@ -327,27 +329,33 @@ public void authTabCallback_withResultOK_setsInternalCallbackResult() {
327329

328330
ArgumentCaptor<ActivityResultCallback<AuthTabIntent.AuthResult>> callbackCaptor =
329331
ArgumentCaptor.forClass(ActivityResultCallback.class);
330-
mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher(
331-
eq(componentActivity),
332-
callbackCaptor.capture()
333-
)).thenReturn(mockLauncher);
334332

335333
when(browserSwitchInspector.isDeviceConfiguredForDeepLinking(
336334
componentActivity.getApplicationContext(),
337335
"return-url-scheme"
338336
)).thenReturn(true);
339337
when(authTabInternalClient.isAuthTabSupported(componentActivity)).thenReturn(true);
340338

341-
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient);
342-
sut.initializeAuthTabLauncher(componentActivity);
339+
mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher(
340+
eq(componentActivity),
341+
callbackCaptor.capture()
342+
)).thenReturn(mockLauncher);
343+
344+
BrowserSwitchClient sut = new BrowserSwitchClient(
345+
componentActivity,
346+
browserSwitchInspector,
347+
authTabInternalClient
348+
);
343349

344350
JSONObject metadata = new JSONObject();
345351
BrowserSwitchOptions options = new BrowserSwitchOptions()
346352
.requestCode(123)
347353
.url(browserSwitchDestinationUrl)
348354
.returnUrlScheme("return-url-scheme")
349355
.metadata(metadata);
350-
sut.start(componentActivity, options);
356+
357+
BrowserSwitchStartResult startResult = sut.start(componentActivity, options);
358+
String pendingRequest = ((BrowserSwitchStartResult.Started) startResult).getPendingRequest();
351359

352360
Uri resultUri = Uri.parse("return-url-scheme://success");
353361
AuthTabIntent.AuthResult mockAuthResult = mock(AuthTabIntent.AuthResult.class, withSettings()
@@ -357,7 +365,7 @@ public void authTabCallback_withResultOK_setsInternalCallbackResult() {
357365
callbackCaptor.getValue().onActivityResult(mockAuthResult);
358366

359367
Intent dummyIntent = new Intent();
360-
BrowserSwitchFinalResult capturedResult = sut.completeRequest(dummyIntent, "dummyPendingRequest");
368+
BrowserSwitchFinalResult capturedResult = sut.completeRequest(dummyIntent, pendingRequest);
361369
assertTrue(capturedResult instanceof BrowserSwitchFinalResult.Success);
362370

363371
BrowserSwitchFinalResult.Success successResult =
@@ -377,9 +385,11 @@ public void authTabCallback_withResultCanceled_callsCallbackWithNoResult() {
377385
callbackCaptor.capture()
378386
)).thenReturn(mockLauncher);
379387

380-
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient);
381-
382-
sut.initializeAuthTabLauncher(componentActivity);
388+
BrowserSwitchClient sut = new BrowserSwitchClient(
389+
componentActivity,
390+
browserSwitchInspector,
391+
authTabInternalClient
392+
);
383393

384394
AuthTabIntent.AuthResult mockAuthResult = mock(AuthTabIntent.AuthResult.class, withSettings()
385395
.useConstructor(AuthTabIntent.RESULT_CANCELED, null)
@@ -395,7 +405,6 @@ public void authTabCallback_withResultCanceled_callsCallbackWithNoResult() {
395405

396406
@Test
397407
public void start_withoutAuthTabLauncher_fallsBackToCustomTabs() {
398-
399408
when(browserSwitchInspector.isDeviceConfiguredForDeepLinking(
400409
componentActivity.getApplicationContext(),
401410
"return-url-scheme"
@@ -431,7 +440,6 @@ public void start_whenAuthTabLauncherIsNull_fallsBackToCustomTabs() {
431440
"return-url-scheme"
432441
)).thenReturn(true);
433442

434-
// Explicitly ensure AuthTab is supported but we still fallback due to null launcher
435443
when(authTabInternalClient.isAuthTabSupported(componentActivity)).thenReturn(true);
436444

437445
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, authTabInternalClient);
@@ -479,10 +487,11 @@ public void isAuthTabSupported_returnsTrueWhenLauncherInitialized() {
479487
any(ActivityResultCallback.class)
480488
)).thenReturn(mockLauncher);
481489

482-
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector,
483-
authTabInternalClient);
484-
485-
sut.initializeAuthTabLauncher(componentActivity);
490+
BrowserSwitchClient sut = new BrowserSwitchClient(
491+
componentActivity,
492+
browserSwitchInspector,
493+
authTabInternalClient
494+
);
486495

487496
boolean result = sut.isAuthTabSupported(applicationContext);
488497

@@ -494,39 +503,35 @@ public void isAuthTabSupported_returnsTrueWhenLauncherInitialized() {
494503
@Test
495504
public void isAuthTabSupported_returnsFalseWhenBrowserDoesNotSupportAuthTab() {
496505
try (MockedStatic<AuthTabIntent> mockedAuthTab = mockStatic(AuthTabIntent.class)) {
497-
when(authTabInternalClient.isAuthTabSupported(applicationContext)).thenReturn(false);
498-
499506
mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher(
500-
any(ComponentActivity.class),
507+
any(ActivityResultCaller.class),
501508
any(ActivityResultCallback.class)
502509
)).thenReturn(mockLauncher);
503510

504-
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector,
505-
authTabInternalClient);
511+
when(authTabInternalClient.isAuthTabSupported(any())).thenReturn(false);
506512

507-
sut.initializeAuthTabLauncher(componentActivity);
513+
BrowserSwitchClient sut = new BrowserSwitchClient(
514+
componentActivity,
515+
browserSwitchInspector,
516+
authTabInternalClient
517+
);
508518

509-
boolean result = sut.isAuthTabSupported(applicationContext);
519+
boolean result = sut.isAuthTabSupported(componentActivity);
510520

511521
assertFalse(result);
512-
verify(authTabInternalClient).isAuthTabSupported(applicationContext);
513522
}
514523
}
515524

516525
@Test
517-
public void parameterizedConstructor_initializesAuthTabLauncher() {
526+
public void defaultConstructor_doesNotInitializeAuthTabLauncher() {
518527
try (MockedStatic<AuthTabIntent> mockedAuthTab = mockStatic(AuthTabIntent.class)) {
519-
mockedAuthTab.when(() -> AuthTabIntent.registerActivityResultLauncher(
520-
any(ComponentActivity.class),
521-
any(ActivityResultCallback.class)
522-
)).thenReturn(mockLauncher);
528+
BrowserSwitchClient sut = new BrowserSwitchClient();
523529

524-
BrowserSwitchClient sut = new BrowserSwitchClient(componentActivity);
530+
mockedAuthTab.verifyNoInteractions();
525531

526-
mockedAuthTab.verify(() -> AuthTabIntent.registerActivityResultLauncher(
527-
eq(componentActivity),
528-
any(ActivityResultCallback.class)
529-
));
532+
when(authTabInternalClient.isAuthTabSupported(applicationContext)).thenReturn(true);
533+
boolean result = sut.isAuthTabSupported(applicationContext);
534+
assertFalse(result);
530535
}
531536
}
532537

0 commit comments

Comments
 (0)