Skip to content

Commit e8c7c85

Browse files
sshropshiresestevenssarahkoop
authored
BrowserSwitchRequest Should Notify Cancellation (#45)
* Add shouldNotify property to BrowserSwitchRequest. Signed-off-by: Steven Shropshire <[email protected]> * Rename shouldNotify to shouldNotifyCancellation in BrowserSwitchRequest. Signed-off-by: Steven Shropshire <[email protected]> * Fix nullability annotations in BrowserSwitchOptions. Signed-off-by: Steven Shropshire <[email protected]> * Update BrowserSwitchClient to notify browser switch result cancellation only once, and keep cancelled request dormant in case a success flow is still possible. Signed-off-by: Steven Shropshire <[email protected]> * Refactor demo app to clear result text when browser switch is started. Signed-off-by: Steven Shropshire <[email protected]> * Fix typo in shared preferences key. Signed-off-by: Steven Shropshire <[email protected]> * Update CHANGELOG. Signed-off-by: Steven Shropshire <[email protected]> * Re-order CHANGELOG. Signed-off-by: Steven Shropshire <[email protected]> * Update comment string in BrowserSwitchClient. Signed-off-by: Steven Shropshire <[email protected]> * Update browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientTest.java Co-authored-by: Sarah Koop <[email protected]> Co-authored-by: Susan Stevens <[email protected]> Co-authored-by: Sarah Koop <[email protected]>
1 parent 7db76e1 commit e8c7c85

File tree

8 files changed

+118
-39
lines changed

8 files changed

+118
-39
lines changed

CHANGELOG.md

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

3+
## unreleased
4+
5+
* Update `BrowserSwitchClient#deliverResult()` to allow canceled browser switches a chance to reach the success state (See braintree_android [#409](https://github.com/braintree/braintree_android/issues/409))
6+
* Fix nullability annotations in `BrowserSwitchOptions` setters
37

48
## 2.0.0
59

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

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void start(@NonNull FragmentActivity activity, @NonNull BrowserSwitchOpti
5252

5353
JSONObject metadata = browserSwitchOptions.getMetadata();
5454
BrowserSwitchRequest request =
55-
new BrowserSwitchRequest(requestCode, browserSwitchUrl, metadata, returnUrlScheme);
55+
new BrowserSwitchRequest(requestCode, browserSwitchUrl, metadata, returnUrlScheme, true);
5656
persistentStore.putActiveRequest(request, appContext);
5757

5858
customTabsInternalClient.launchUrl(activity, browserSwitchUrl);
@@ -116,17 +116,20 @@ public BrowserSwitchResult deliverResult(@NonNull FragmentActivity activity) {
116116
return null;
117117
}
118118

119-
BrowserSwitchResult result;
119+
BrowserSwitchResult result = null;
120120

121121
Uri deepLinkUrl = intent.getData();
122122
if (deepLinkUrl != null && request.matchesDeepLinkUrlScheme(deepLinkUrl)) {
123123
result = new BrowserSwitchResult(BrowserSwitchStatus.SUCCESS, request, deepLinkUrl);
124-
} else {
124+
persistentStore.clearActiveRequest(appContext);
125+
} else if (request.getShouldNotifyCancellation()) {
126+
// ensure that cancellation result is delivered exactly one time
125127
result = new BrowserSwitchResult(BrowserSwitchStatus.CANCELED, request);
128+
129+
request.setShouldNotifyCancellation(false);
130+
persistentStore.putActiveRequest(request, activity);
126131
}
127132

128-
// ensure that browser switch result is delivered exactly one time
129-
persistentStore.clearActiveRequest(appContext);
130133
return result;
131134
}
132135
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public class BrowserSwitchOptions {
2727
* {@link BrowserSwitchResult} when the app has re-entered the foreground
2828
* @return {@link BrowserSwitchOptions} reference to instance to allow setter invocations to be chained
2929
*/
30-
public BrowserSwitchOptions metadata(@NonNull JSONObject metadata) {
30+
public BrowserSwitchOptions metadata(@Nullable JSONObject metadata) {
3131
this.metadata = metadata;
3232
return this;
3333
}
@@ -49,7 +49,7 @@ public BrowserSwitchOptions requestCode(int requestCode) {
4949
* @param url The target url to use for browser switch
5050
* @return {@link BrowserSwitchOptions} reference to instance to allow setter invocations to be chained
5151
*/
52-
public BrowserSwitchOptions url(@NonNull Uri url) {
52+
public BrowserSwitchOptions url(@Nullable Uri url) {
5353
this.url = url;
5454
return this;
5555
}
@@ -61,7 +61,7 @@ public BrowserSwitchOptions url(@NonNull Uri url) {
6161
* after browser switch
6262
* @return {@link BrowserSwitchOptions} reference to instance to allow setter invocations to be chained
6363
*/
64-
public BrowserSwitchOptions returnUrlScheme(@NonNull String returnUrlScheme) {
64+
public BrowserSwitchOptions returnUrlScheme(@Nullable String returnUrlScheme) {
6565
this.returnUrlScheme = returnUrlScheme;
6666
return this;
6767
}

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

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,24 @@ class BrowserSwitchRequest {
1313
private final int requestCode;
1414
private final JSONObject metadata;
1515
private final String returnUrlScheme;
16+
private boolean shouldNotifyCancellation;
1617

1718
static BrowserSwitchRequest fromJson(String json) throws JSONException {
1819
JSONObject jsonObject = new JSONObject(json);
1920
int requestCode = jsonObject.getInt("requestCode");
2021
String url = jsonObject.getString("url");
2122
String returnUrlScheme = jsonObject.getString("returnUrlScheme");
2223
JSONObject metadata = jsonObject.optJSONObject("metadata");
23-
return new BrowserSwitchRequest(requestCode, Uri.parse(url), metadata, returnUrlScheme);
24+
boolean shouldNotify = jsonObject.optBoolean("shouldNotify", true);
25+
return new BrowserSwitchRequest(requestCode, Uri.parse(url), metadata, returnUrlScheme, shouldNotify);
2426
}
2527

26-
BrowserSwitchRequest(int requestCode, Uri url, JSONObject metadata, String returnUrlScheme) {
28+
BrowserSwitchRequest(int requestCode, Uri url, JSONObject metadata, String returnUrlScheme, boolean shouldNotifyCancellation) {
2729
this.url = url;
2830
this.requestCode = requestCode;
2931
this.metadata = metadata;
3032
this.returnUrlScheme = returnUrlScheme;
33+
this.shouldNotifyCancellation = shouldNotifyCancellation;
3134
}
3235

3336
Uri getUrl() {
@@ -42,11 +45,20 @@ JSONObject getMetadata() {
4245
return metadata;
4346
}
4447

48+
boolean getShouldNotifyCancellation() {
49+
return shouldNotifyCancellation;
50+
}
51+
52+
void setShouldNotifyCancellation(boolean shouldNotifyCancellation) {
53+
this.shouldNotifyCancellation = shouldNotifyCancellation;
54+
}
55+
4556
String toJson() throws JSONException {
4657
JSONObject result = new JSONObject();
4758
result.put("requestCode", requestCode);
4859
result.put("url", url.toString());
4960
result.put("returnUrlScheme", returnUrlScheme);
61+
result.put("shouldNotify", shouldNotifyCancellation);
5062
if (metadata != null) {
5163
result.put("metadata", metadata);
5264
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class PersistentStore {
99

1010
@VisibleForTesting
1111
static final String PREFERENCES_KEY =
12-
"com.braintreepayament.browserswitch.persistentstore";
12+
"com.braintreepayment.browserswitch.persistentstore";
1313

1414
static void put(String key, String value, Context context) {
1515
Context applicationContext = context.getApplicationContext();

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

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,13 @@
1616
import org.robolectric.RobolectricTestRunner;
1717

1818
import static org.junit.Assert.assertEquals;
19+
import static org.junit.Assert.assertFalse;
1920
import static org.junit.Assert.assertNotNull;
2021
import static org.junit.Assert.assertNull;
2122
import static org.junit.Assert.assertSame;
23+
import static org.junit.Assert.assertTrue;
2224
import static org.junit.Assert.fail;
25+
import static org.mockito.ArgumentMatchers.any;
2326
import static org.mockito.ArgumentMatchers.same;
2427
import static org.mockito.Mockito.mock;
2528
import static org.mockito.Mockito.never;
@@ -82,6 +85,7 @@ public void start_createsBrowserSwitchIntentAndInitiatesBrowserSwitch() throws B
8285
assertEquals(browserSwitchRequest.getRequestCode(), 123);
8386
assertEquals(browserSwitchRequest.getUrl(), browserSwitchDestinationUrl);
8487
assertSame(browserSwitchRequest.getMetadata(), metadata);
88+
assertTrue(browserSwitchRequest.getShouldNotifyCancellation());
8589
}
8690

8791
@Test
@@ -181,7 +185,7 @@ public void deliverResult_whenDeepLinkUrlExistsAndReturnUrlSchemeMatches_clearsR
181185
when(activity.getIntent()).thenReturn(deepLinkIntent);
182186

183187
JSONObject requestMetadata = new JSONObject();
184-
BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme");
188+
BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme", false);
185189
when(persistentStore.getActiveRequest(applicationContext)).thenReturn(request);
186190

187191
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, persistentStore, customTabsInternalClient);
@@ -198,15 +202,15 @@ public void deliverResult_whenDeepLinkUrlExistsAndReturnUrlSchemeMatches_clearsR
198202
}
199203

200204
@Test
201-
public void deliverResult_whenDeepLinkUrlExistsAndReturnUrlSchemeDoesNotMatch_clearsResultStoreAndNotifiesResultCANCELED() {
205+
public void deliverResult_whenDeepLinkUrlExists_AndReturnUrlSchemeDoesNotMatch_AndShouldNotifyCancellation_notifiesResultCANCELED_AndSetsRequestShouldNotifyCancellationToFalse() {
202206
when(activity.getApplicationContext()).thenReturn(applicationContext);
203207

204208
Uri deepLinkUrl = Uri.parse("another-return-url-scheme://test");
205209
Intent deepLinkIntent = new Intent().setData(deepLinkUrl);
206210
when(activity.getIntent()).thenReturn(deepLinkIntent);
207211

208212
JSONObject requestMetadata = new JSONObject();
209-
BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme");
213+
BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme", true);
210214
when(persistentStore.getActiveRequest(applicationContext)).thenReturn(request);
211215

212216
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, persistentStore, customTabsInternalClient);
@@ -219,18 +223,41 @@ public void deliverResult_whenDeepLinkUrlExistsAndReturnUrlSchemeDoesNotMatch_cl
219223
assertSame(result.getRequestMetadata(), requestMetadata);
220224
assertNull(result.getDeepLinkUrl());
221225

222-
verify(persistentStore).clearActiveRequest(applicationContext);
226+
ArgumentCaptor<BrowserSwitchRequest> captor = ArgumentCaptor.forClass(BrowserSwitchRequest.class);
227+
verify(persistentStore).putActiveRequest(captor.capture(), same(activity));
228+
229+
BrowserSwitchRequest updatedRequest = captor.getValue();
230+
assertSame(request, updatedRequest);
231+
assertFalse(updatedRequest.getShouldNotifyCancellation());
232+
}
223233

234+
@Test
235+
public void deliverResult_whenDeepLinkUrlExistsAndReturnUrlSchemeDoesNotMatchAndShouldNotNotifyCancellation_returnsNull() {
236+
when(activity.getApplicationContext()).thenReturn(applicationContext);
237+
238+
Uri deepLinkUrl = Uri.parse("another-return-url-scheme://test");
239+
Intent deepLinkIntent = new Intent().setData(deepLinkUrl);
240+
when(activity.getIntent()).thenReturn(deepLinkIntent);
241+
242+
JSONObject requestMetadata = new JSONObject();
243+
BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme", false);
244+
when(persistentStore.getActiveRequest(applicationContext)).thenReturn(request);
245+
246+
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, persistentStore, customTabsInternalClient);
247+
BrowserSwitchResult result = sut.deliverResult(activity);
248+
249+
assertNull(result);
250+
verify(persistentStore, never()).putActiveRequest(any(BrowserSwitchRequest.class), any(FragmentActivity.class));
224251
}
225252

226253
@Test
227-
public void deliverResult_whenDeepLinkUrlDoesNotExist_clearsResultStoreAndNotifiesResultCANCELED() {
254+
public void deliverResult_whenDeepLinkUrlDoesNotExistAndShouldNotifyCancellation_notifiesResultCANCELEDAndSetsRequestShouldNotifyCancellationToFalse() {
228255
when(activity.getApplicationContext()).thenReturn(applicationContext);
229256
when(activity.getIntent()).thenReturn(new Intent());
230257

231258
JSONObject requestMetadata = new JSONObject();
232259
BrowserSwitchRequest request =
233-
new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme");
260+
new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme", true);
234261
when(persistentStore.getActiveRequest(applicationContext)).thenReturn(request);
235262

236263
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, persistentStore, customTabsInternalClient);
@@ -243,7 +270,29 @@ public void deliverResult_whenDeepLinkUrlDoesNotExist_clearsResultStoreAndNotifi
243270
assertSame(result.getRequestMetadata(), requestMetadata);
244271
assertNull(result.getDeepLinkUrl());
245272

246-
verify(persistentStore).clearActiveRequest(applicationContext);
273+
ArgumentCaptor<BrowserSwitchRequest> captor = ArgumentCaptor.forClass(BrowserSwitchRequest.class);
274+
verify(persistentStore).putActiveRequest(captor.capture(), same(activity));
275+
276+
BrowserSwitchRequest updatedRequest = captor.getValue();
277+
assertSame(request, updatedRequest);
278+
assertFalse(updatedRequest.getShouldNotifyCancellation());
279+
}
280+
281+
@Test
282+
public void deliverResult_whenDeepLinkUrlDoesNotExistAndShouldNotNotifyCancellation_returnsNull() {
283+
when(activity.getApplicationContext()).thenReturn(applicationContext);
284+
when(activity.getIntent()).thenReturn(new Intent());
285+
286+
JSONObject requestMetadata = new JSONObject();
287+
BrowserSwitchRequest request =
288+
new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "return-url-scheme", false);
289+
when(persistentStore.getActiveRequest(applicationContext)).thenReturn(request);
290+
291+
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, persistentStore, customTabsInternalClient);
292+
BrowserSwitchResult result = sut.deliverResult(activity);
293+
294+
assertNull(result);
295+
verify(persistentStore, never()).putActiveRequest(any(BrowserSwitchRequest.class), any(FragmentActivity.class));
247296
}
248297

249298
@Test

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,30 @@
1818
public class BrowserSwitchRequestUnitTest {
1919

2020
@Test
21-
public void fromJson_withoutMetadata() throws JSONException {
21+
public void fromJson_withoutShouldNotifyProperty_defaultsShouldNotifyToTrue() throws JSONException {
2222
String json = "{\n" +
2323
" \"requestCode\": 123,\n" +
2424
" \"url\": \"https://example.com\",\n" +
2525
" \"returnUrlScheme\": \"my-return-url-scheme\"\n" +
2626
"}";
2727
BrowserSwitchRequest sut = BrowserSwitchRequest.fromJson(json);
28+
assertTrue(sut.getShouldNotifyCancellation());
29+
}
30+
31+
@Test
32+
public void fromJson_withoutMetadata() throws JSONException {
33+
String json = "{\n" +
34+
" \"requestCode\": 123,\n" +
35+
" \"url\": \"https://example.com\",\n" +
36+
" \"returnUrlScheme\": \"my-return-url-scheme\",\n" +
37+
" \"shouldNotify\": true\n" +
38+
"}";
39+
BrowserSwitchRequest sut = BrowserSwitchRequest.fromJson(json);
2840

2941
assertEquals(sut.getRequestCode(), 123);
3042
assertEquals(sut.getUrl().toString(), "https://example.com");
3143
assertNull(sut.getMetadata());
44+
assertTrue(sut.getShouldNotifyCancellation());
3245

3346
assertTrue(sut.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test")));
3447
assertFalse(sut.matchesDeepLinkUrlScheme(Uri.parse("another-return-url-scheme://test")));
@@ -39,14 +52,16 @@ public void toJson_withoutMetadata() throws JSONException {
3952
String json = "{\n" +
4053
" \"requestCode\": 123,\n" +
4154
" \"url\": \"https://example.com\",\n" +
42-
" \"returnUrlScheme\": \"my-return-url-scheme\"\n" +
55+
" \"returnUrlScheme\": \"my-return-url-scheme\",\n" +
56+
" \"shouldNotify\": false\n" +
4357
"}";
4458
BrowserSwitchRequest original = BrowserSwitchRequest.fromJson(json);
4559
BrowserSwitchRequest restored = BrowserSwitchRequest.fromJson(original.toJson());
4660

4761
assertEquals(restored.getRequestCode(), original.getRequestCode());
4862
assertEquals(restored.getUrl(), original.getUrl());
4963
assertNull(restored.getMetadata());
64+
assertFalse(restored.getShouldNotifyCancellation());
5065

5166
assertTrue(restored.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test")));
5267
assertFalse(restored.matchesDeepLinkUrlScheme(Uri.parse("another-return-url-scheme://test")));
@@ -58,6 +73,7 @@ public void fromJson_withMetadata() throws JSONException {
5873
" \"requestCode\": 123,\n" +
5974
" \"url\": \"https://example.com\",\n" +
6075
" \"returnUrlScheme\": \"my-return-url-scheme\",\n" +
76+
" \"shouldNotify\": true,\n" +
6177
" \"metadata\": {\n" +
6278
" \"testKey\": \"testValue\"" +
6379
" }" +
@@ -66,6 +82,7 @@ public void fromJson_withMetadata() throws JSONException {
6682

6783
assertEquals(sut.getRequestCode(), 123);
6884
assertEquals(sut.getUrl().toString(), "https://example.com");
85+
assertTrue(sut.getShouldNotifyCancellation());
6986
JSONAssert.assertEquals(sut.getMetadata(), new JSONObject().put("testKey", "testValue"), true);
7087

7188
assertTrue(sut.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test")));
@@ -78,6 +95,7 @@ public void toJson_withMetadata() throws JSONException {
7895
" \"requestCode\": 123,\n" +
7996
" \"url\": \"https://example.com\",\n" +
8097
" \"returnUrlScheme\": \"my-return-url-scheme\",\n" +
98+
" \"shouldNotify\": false,\n" +
8199
" \"metadata\": {\n" +
82100
" \"testKey\": \"testValue\"" +
83101
" }" +
@@ -87,6 +105,7 @@ public void toJson_withMetadata() throws JSONException {
87105

88106
assertEquals(restored.getRequestCode(), original.getRequestCode());
89107
assertEquals(restored.getUrl(), original.getUrl());
108+
assertFalse(restored.getShouldNotifyCancellation());
90109
JSONAssert.assertEquals(restored.getMetadata(), original.getMetadata(), true);
91110

92111
assertTrue(restored.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test")));

demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,38 +53,24 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c
5353
public void onClick(View v) {
5454
@IdRes int viewId = v.getId();
5555
if (viewId == R.id.browser_switch) {
56-
startBrowserSwitch();
56+
startBrowserSwitch(null);
5757
} else if (viewId == R.id.browser_switch_with_metadata) {
5858
JSONObject metadata = buildMetadataObject();
59-
startBrowserSwitchWithMetadata(metadata);
59+
startBrowserSwitch(metadata);
6060
}
6161
}
6262

63-
private void startBrowserSwitch() {
64-
Uri url = buildBrowserSwitchUrl();
65-
BrowserSwitchOptions browserSwitchOptions = new BrowserSwitchOptions()
66-
.requestCode(1)
67-
.url(url)
68-
.returnUrlScheme(getReturnUrlScheme());
69-
70-
try {
71-
getDemoActivity().startBrowserSwitch(browserSwitchOptions);
72-
} catch (BrowserSwitchException e) {
73-
String statusText = "Browser Switch Error: " + e.getMessage();
74-
mBrowserSwitchStatusTextView.setText(statusText);
75-
e.printStackTrace();
76-
}
77-
}
78-
79-
private void startBrowserSwitchWithMetadata(JSONObject metadata) {
63+
private void startBrowserSwitch(@Nullable JSONObject metadata) {
8064
Uri url = buildBrowserSwitchUrl();
8165
BrowserSwitchOptions browserSwitchOptions = new BrowserSwitchOptions()
8266
.metadata(metadata)
8367
.requestCode(1)
8468
.url(url)
8569
.returnUrlScheme(getReturnUrlScheme());
70+
8671
try {
8772
getDemoActivity().startBrowserSwitch(browserSwitchOptions);
73+
clearTextViews();
8874
} catch (BrowserSwitchException e) {
8975
String statusText = "Browser Switch Error: " + e.getMessage();
9076
mBrowserSwitchStatusTextView.setText(statusText);
@@ -108,6 +94,12 @@ private JSONObject buildMetadataObject() {
10894
return null;
10995
}
11096

97+
private void clearTextViews() {
98+
mBrowserSwitchStatusTextView.setText("");
99+
mSelectedColorTextView.setText("");
100+
mMetadataTextView.setText("");
101+
}
102+
111103
public void onBrowserSwitchResult(BrowserSwitchResult result) {
112104
String resultText = null;
113105
String selectedColorText = "";

0 commit comments

Comments
 (0)