From 70ff25f076e715678414a56eb0c916fe51676cc2 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 26 Mar 2024 12:51:49 -0500 Subject: [PATCH 01/13] Return base64 encoded string from browser switch. --- .../api/BrowserSwitchClient.java | 35 +++++---- .../api/BrowserSwitchException.java | 4 ++ .../api/BrowserSwitchPendingRequest.kt | 25 +------ .../api/BrowserSwitchRequest.java | 69 ++++++++++-------- .../api/BrowserSwitchResult.kt | 17 ++++- .../api/BrowserSwitchResultInfo.java | 5 +- .../api/BrowserSwitchClientUnitTest.java | 8 +-- .../BrowserSwitchPendingRequestUnitTest.kt | 13 ++-- .../api/BrowserSwitchResultInfoUnitTest.java | 2 +- .../api/demo/ComposeActivity.kt | 71 ++++++++++++------- .../api/demo/DemoActivitySingleTop.java | 2 +- .../api/demo/DemoFragment.java | 9 ++- .../api/demo/utils/PendingRequestStore.kt | 2 +- .../demo/viewmodel/BrowserSwitchViewModel.kt | 3 +- .../api/demo/viewmodel/UiState.kt | 6 +- 15 files changed, 154 insertions(+), 117 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 45ca0b99..8636fa71 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -12,6 +12,7 @@ import com.braintreepayments.api.browserswitch.R; +import org.json.JSONException; import org.json.JSONObject; /** @@ -41,7 +42,7 @@ public BrowserSwitchClient() { * Open a browser or Chrome Custom Tab * with a given set of {@link BrowserSwitchOptions} from an Android activity. * - * @param activity the activity used to start browser switch + * @param activity the activity used to start browser switch * @param browserSwitchOptions {@link BrowserSwitchOptions} the options used to configure the browser switch * @return a {@link BrowserSwitchPendingRequest.Started} that should be stored and passed to * {@link BrowserSwitchClient#parseResult(BrowserSwitchPendingRequest.Started, Intent)} upon return to the app, @@ -65,17 +66,16 @@ public BrowserSwitchPendingRequest start(@NonNull ComponentActivity activity, @N String activityFinishingMessage = "Unable to start browser switch while host Activity is finishing."; return new BrowserSwitchPendingRequest.Failure(new BrowserSwitchException(activityFinishingMessage)); - } else { + } else { boolean launchAsNewTask = browserSwitchOptions.isLaunchAsNewTask(); - BrowserSwitchRequest request; try { - request = - new BrowserSwitchRequest(requestCode, browserSwitchUrl, metadata, returnUrlScheme, true); + BrowserSwitchRequest request = + new BrowserSwitchRequest(requestCode, browserSwitchUrl, metadata, returnUrlScheme); customTabsInternalClient.launchUrl(activity, browserSwitchUrl, launchAsNewTask); - } catch (ActivityNotFoundException e) { - return new BrowserSwitchPendingRequest.Failure(new BrowserSwitchException("Unable to start browser switch without a web browser.")); + return new BrowserSwitchPendingRequest.Started(request.tokenize()); + } catch (ActivityNotFoundException | BrowserSwitchException e) { + return new BrowserSwitchPendingRequest.Failure(new BrowserSwitchException("Unable to start browser switch without a web browser.", e)); } - return new BrowserSwitchPendingRequest.Started(request); } } @@ -106,10 +106,11 @@ private boolean isValidRequestCode(int requestCode) { /** * Parses and returns a browser switch result if a match is found for the given {@link BrowserSwitchRequest} + * * @param pendingRequest the {@link BrowserSwitchPendingRequest.Started} returned from - * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} - * @param intent the intent to return to your application containing a deep link result from the - * browser flow + * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} + * @param intent the intent to return to your application containing a deep link result from the + * browser flow * @return a {@link BrowserSwitchResult.Success} if the browser switch was successfully * completed, or {@link BrowserSwitchResult.NoResult} if no result can be found for the given * {@link BrowserSwitchPendingRequest.Started}. A {@link BrowserSwitchResult.NoResult} will be @@ -118,9 +119,15 @@ private boolean isValidRequestCode(int requestCode) { public BrowserSwitchResult parseResult(@NonNull BrowserSwitchPendingRequest.Started pendingRequest, @Nullable Intent intent) { if (intent != null && intent.getData() != null) { Uri deepLinkUrl = intent.getData(); - if (pendingRequest.getBrowserSwitchRequest().matchesDeepLinkUrlScheme(deepLinkUrl)) { - BrowserSwitchResultInfo resultInfo = new BrowserSwitchResultInfo(pendingRequest.getBrowserSwitchRequest(), deepLinkUrl); - return new BrowserSwitchResult.Success(resultInfo); + try { + BrowserSwitchRequest originalRequest = + BrowserSwitchRequest.fromToken(pendingRequest.getToken()); + if (originalRequest.matchesDeepLinkUrlScheme(deepLinkUrl)) { + return new BrowserSwitchResult.Success(deepLinkUrl, originalRequest); + } + } catch (BrowserSwitchException e) { + // TODO: handle error + throw new RuntimeException(e); } } return BrowserSwitchResult.NoResult.INSTANCE; diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchException.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchException.java index d2328fbd..6b33e995 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchException.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchException.java @@ -8,4 +8,8 @@ public class BrowserSwitchException extends Exception { BrowserSwitchException(String message) { super(message); } + + BrowserSwitchException(String message, Exception reason) { + super(message, reason); + } } diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt index a0367b57..e3bfe110 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt @@ -1,7 +1,5 @@ package com.braintreepayments.api -import org.json.JSONException - /** * A pending request for browser switching. This pending request should be stored locally within the app or * on-device and used to deliver a result of the browser flow in [BrowserSwitchClient.parseResult] @@ -11,27 +9,8 @@ sealed class BrowserSwitchPendingRequest { /** * A browser switch was successfully started. This pending request should be store dnd passed to * [BrowserSwitchClient.parseResult] - */ - class Started(val browserSwitchRequest: BrowserSwitchRequest) : BrowserSwitchPendingRequest() { - - /** - * Convenience constructor to create a [BrowserSwitchPendingRequest.Started] from your stored - * [String] from [BrowserSwitchPendingRequest.Started.toJsonString]. - * @throws [JSONException] if the [jsonString] is invalid. - */ - @Throws(JSONException::class) - constructor(jsonString: String) : this( - BrowserSwitchRequest.fromJson(jsonString) - ) - - /** - * Convenience method to return [BrowserSwitchPendingRequest.Started] in [String] format to be - * persisted in storage - */ - fun toJsonString(): String { - return browserSwitchRequest.toJson() - } - } + */ + class Started(val token: String) : BrowserSwitchPendingRequest() /** * An error with [cause] occurred launching the browser diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java index f4b94f6a..415985f9 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java @@ -1,6 +1,7 @@ package com.braintreepayments.api; import android.net.Uri; +import android.util.Base64; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -8,32 +9,47 @@ import org.json.JSONException; import org.json.JSONObject; +import java.nio.charset.StandardCharsets; + +// Links +// Base64 Encode a String in Android: https://stackoverflow.com/a/7360440 + public class BrowserSwitchRequest { + private static final String KEY_REQUEST_CODE = "requestCode"; + private static final String KEY_URL = "url"; + private static final String KEY_RETURN_URL_SCHEME = "returnUrlScheme"; + private static final String KEY_METADATA = "metadata"; private final Uri url; private final int requestCode; private final JSONObject metadata; @VisibleForTesting final String returnUrlScheme; - private boolean shouldNotifyCancellation; - - static BrowserSwitchRequest fromJson(String json) throws JSONException { - JSONObject jsonObject = new JSONObject(json); - int requestCode = jsonObject.getInt("requestCode"); - String url = jsonObject.getString("url"); - String returnUrlScheme = jsonObject.getString("returnUrlScheme"); - JSONObject metadata = jsonObject.optJSONObject("metadata"); - boolean shouldNotify = jsonObject.optBoolean("shouldNotify", true); - return new BrowserSwitchRequest(requestCode, Uri.parse(url), metadata, returnUrlScheme, shouldNotify); + + @NonNull + static BrowserSwitchRequest fromToken(@NonNull String tokenBase64) throws BrowserSwitchException { + byte[] data = Base64.decode(tokenBase64, Base64.DEFAULT); + String token = new String(data, StandardCharsets.UTF_8); + + try { + JSONObject tokenJSON = new JSONObject(token); + return new BrowserSwitchRequest( + tokenJSON.getInt(KEY_REQUEST_CODE), + Uri.parse(tokenJSON.getString(KEY_URL)), + tokenJSON.getJSONObject(KEY_METADATA), + tokenJSON.getString(KEY_RETURN_URL_SCHEME) + ); + } catch (JSONException e) { + throw new BrowserSwitchException("Unable to decode browser switch state from token.", e); + } } - BrowserSwitchRequest(int requestCode, Uri url, JSONObject metadata, String returnUrlScheme, boolean shouldNotifyCancellation) { + BrowserSwitchRequest(int requestCode, Uri url, JSONObject metadata, String returnUrlScheme) { this.url = url; this.requestCode = requestCode; this.metadata = metadata; this.returnUrlScheme = returnUrlScheme; - this.shouldNotifyCancellation = shouldNotifyCancellation; } Uri getUrl() { @@ -48,24 +64,21 @@ JSONObject getMetadata() { return metadata; } - boolean getShouldNotifyCancellation() { - return shouldNotifyCancellation; - } - - void setShouldNotifyCancellation(boolean shouldNotifyCancellation) { - this.shouldNotifyCancellation = shouldNotifyCancellation; - } + @NonNull + String tokenize() throws BrowserSwitchException { + try { + // TODO: make return url scheme accessor public + JSONObject tokenJSON = new JSONObject() + .put(KEY_REQUEST_CODE, requestCode) + .put(KEY_URL, url.toString()) + .put(KEY_RETURN_URL_SCHEME, returnUrlScheme) + .putOpt(KEY_METADATA, metadata); - String toJson() throws JSONException { - JSONObject result = new JSONObject(); - result.put("requestCode", requestCode); - result.put("url", url.toString()); - result.put("returnUrlScheme", returnUrlScheme); - result.put("shouldNotify", shouldNotifyCancellation); - if (metadata != null) { - result.put("metadata", metadata); + byte[] tokenBytes = tokenJSON.toString().getBytes(StandardCharsets.UTF_8); + return Base64.encodeToString(tokenBytes, Base64.DEFAULT); + } catch (JSONException e) { + throw new BrowserSwitchException("Unable to tokenize Browser Switch State", e); } - return result.toString(); } boolean matchesDeepLinkUrlScheme(@NonNull Uri url) { diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt index b01b461d..a8fe226e 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt @@ -1,5 +1,8 @@ package com.braintreepayments.api +import android.net.Uri +import org.json.JSONObject + /** * The result of a browser switch obtained from [BrowserSwitchClient.parseResult] */ @@ -8,7 +11,19 @@ sealed class BrowserSwitchResult { /** * The browser switch was successfully completed. See [resultInfo] for details. */ - class Success(val resultInfo: BrowserSwitchResultInfo) : BrowserSwitchResult() + class Success( + val deepLinkUrl: Uri, + val requestCode: Int, + val requestUrl: Uri, + val requestMetadata: JSONObject?, + ) : BrowserSwitchResult() { + constructor(deepLinkUrl: Uri, originalRequest: BrowserSwitchRequest) : this( + deepLinkUrl, + originalRequest.requestCode, + originalRequest.url, + originalRequest.metadata + ) + } /** * No browser switch result was found. This is the expected result when a user cancels the diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java index b77f2e98..eec5adc6 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java @@ -22,7 +22,8 @@ static BrowserSwitchResultInfo fromJson(String json) throws JSONException { JSONObject jsonObject = new JSONObject(json); String deepLinkUrl = jsonObject.getString(KEY_DEEP_LINK_URL); String browserSwitchRequest = jsonObject.getString(KEY_BROWSER_SWITCH_REQUEST); - return new BrowserSwitchResultInfo(BrowserSwitchRequest.fromJson(browserSwitchRequest), Uri.parse(deepLinkUrl)); + return null; +// return new BrowserSwitchResultInfo(BrowserSwitchRequest.fromJson(browserSwitchRequest), Uri.parse(deepLinkUrl)); } BrowserSwitchResultInfo(BrowserSwitchRequest request, Uri deepLinkUrl) { @@ -64,7 +65,7 @@ public Uri getDeepLinkUrl() { public String toJson() throws JSONException { JSONObject result = new JSONObject(); result.put(KEY_DEEP_LINK_URL, deepLinkUrl.toString()); - result.put(KEY_BROWSER_SWITCH_REQUEST, request.toJson()); +// result.put(KEY_BROWSER_SWITCH_REQUEST, request.toJson()); return result.toString(); } } 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 fbf7efaf..12217859 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -94,7 +94,7 @@ public void start_whenSuccessful_returnsBrowserSwitchRequest() { assertNotNull(browserSwitchPendingRequest); assertTrue(browserSwitchPendingRequest instanceof BrowserSwitchPendingRequest.Started); - BrowserSwitchRequest browserSwitchRequest = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getBrowserSwitchRequest(); + BrowserSwitchRequest browserSwitchRequest = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getToken(); assertEquals(browserSwitchRequest.getRequestCode(), 123); assertEquals(browserSwitchRequest.getUrl(), browserSwitchDestinationUrl); assertSame(browserSwitchRequest.getMetadata(), metadata); @@ -184,7 +184,7 @@ public void parseResult_whenActiveRequestMatchesDeepLinkResultURLScheme_returnsB JSONObject requestMetadata = new JSONObject(); BrowserSwitchRequest request = - new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme", false); + new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); Uri deepLinkUrl = Uri.parse("fake-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); @@ -201,7 +201,7 @@ public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() JSONObject requestMetadata = new JSONObject(); BrowserSwitchRequest request = - new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme", false); + new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); Uri deepLinkUrl = Uri.parse("a-different-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); @@ -217,7 +217,7 @@ public void parseResult_whenIntentIsNull_returnsNoResult() { JSONObject requestMetadata = new JSONObject(); BrowserSwitchRequest request = - new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme", false); + new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), null); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt index 5bbecba8..4baa07a0 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt @@ -15,8 +15,7 @@ class BrowserSwitchPendingRequestUnitTest { 1, Uri.parse("http://"), JSONObject().put("test_key", "test_value"), - "return-url-scheme", - false + "return-url-scheme" ) @Test @@ -25,17 +24,17 @@ class BrowserSwitchPendingRequestUnitTest { val storedRequest = pendingRequest.toJsonString() val sut = BrowserSwitchPendingRequest.Started(storedRequest) - assertEquals(browserSwitchRequest.requestCode, sut.browserSwitchRequest.requestCode) + assertEquals(browserSwitchRequest.requestCode, sut.token.requestCode) assertEquals( browserSwitchRequest.metadata.getString("test_key"), - sut.browserSwitchRequest.metadata.getString("test_key") + sut.token.metadata.getString("test_key") ) - assertEquals(browserSwitchRequest.url, sut.browserSwitchRequest.url) + assertEquals(browserSwitchRequest.url, sut.token.url) assertEquals( browserSwitchRequest.shouldNotifyCancellation, - sut.browserSwitchRequest.shouldNotifyCancellation + sut.token.shouldNotifyCancellation ) - assertEquals(browserSwitchRequest.returnUrlScheme, sut.browserSwitchRequest.returnUrlScheme) + assertEquals(browserSwitchRequest.returnUrlScheme, sut.token.returnUrlScheme) } @Test(expected = JSONException::class) diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java index 50ab4e1d..9d00cc04 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java @@ -21,7 +21,7 @@ public void toJSON_serializesResult() throws JSONException { JSONObject requestMetadata = new JSONObject() .put("sample", "value"); BrowserSwitchRequest request = - new BrowserSwitchRequest(123, requestUrl, requestMetadata, returnUrlScheme, true); + new BrowserSwitchRequest(123, requestUrl, requestMetadata, returnUrlScheme); Uri deepLinkUrl = Uri.parse("example.return.url.scheme://success/ok"); BrowserSwitchResultInfo diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index 55509574..04676874 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -37,7 +37,7 @@ class ComposeActivity : ComponentActivity() { browserSwitchClient = BrowserSwitchClient() setContent { - Column (modifier = Modifier.padding(10.dp)) { + Column(modifier = Modifier.padding(10.dp)) { BrowserSwitchButton { startBrowserSwitch() } @@ -48,16 +48,22 @@ class ComposeActivity : ComponentActivity() { override fun onResume() { super.onResume() - PendingRequestStore.get(this)?.let { startedRequest -> - when (val browserSwitchResult = - browserSwitchClient.parseResult(startedRequest, intent)) { - is BrowserSwitchResult.Success -> viewModel.browserSwitchResult = - browserSwitchResult.resultInfo - - is BrowserSwitchResult.NoResult -> viewModel.browserSwitchError = - Exception("User did not complete browser switch") + val pendingRequestToken = PendingRequestStore.get(this) + val browserSwitchResult = pendingRequestToken?.let { + browserSwitchClient.parseResult(pendingRequestToken, intent) + } + when (browserSwitchResult) { + is BrowserSwitchResult.Success -> { + viewModel.browserSwitchResult = browserSwitchResult + PendingRequestStore.clear(this) } - PendingRequestStore.clear(this) + + is BrowserSwitchResult.NoResult -> + viewModel.browserSwitchError = Exception("User did not complete browser switch") + + // ignore null case + // TODO: Make parse result @NonNull + else -> {} } } @@ -71,7 +77,8 @@ class ComposeActivity : ComponentActivity() { .returnUrlScheme(RETURN_URL_SCHEME) when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { is BrowserSwitchPendingRequest.Started -> PendingRequestStore.put(this, pendingRequest) - is BrowserSwitchPendingRequest.Failure -> viewModel.browserSwitchError = pendingRequest.cause + is BrowserSwitchPendingRequest.Failure -> viewModel.browserSwitchError = + pendingRequest.cause } } @@ -82,7 +89,7 @@ class ComposeActivity : ComponentActivity() { } private fun buildMetadataObject(): JSONObject? { - return JSONObject().put("test_key","test_value") + return JSONObject().put("test_key", "test_value") } companion object { @@ -102,24 +109,31 @@ fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { @Composable fun BrowserSwitchButton(onClick: () -> Unit) { - Button(modifier = Modifier.fillMaxWidth(), - onClick = onClick) { + Button( + modifier = Modifier.fillMaxWidth(), + onClick = onClick + ) { Text(text = "Start Browser Switch") } } @Composable -fun BrowserSwitchSuccess(result: BrowserSwitchResultInfo) { - result.deepLinkUrl?.let { returnUrl -> - val color = 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) - } +fun BrowserSwitchSuccess(result: BrowserSwitchResult.Success) { + val returnUrl = result.deepLinkUrl + + val color = 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) } } } @@ -127,7 +141,12 @@ fun BrowserSwitchSuccess(result: BrowserSwitchResultInfo) { @Composable fun BrowserSwitchError(exception: Exception) { Column(modifier = Modifier.padding(10.dp)) { - Text(fontSize = 20.sp, fontWeight = FontWeight.Bold, color = Color.White, text = "Browser Switch Error") + 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/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java index fc34a1bf..f841ead0 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java @@ -47,7 +47,7 @@ protected void onNewIntent(Intent intent) { if (pendingRequest != null) { BrowserSwitchResult result = browserSwitchClient.parseResult(pendingRequest, intent); if (result instanceof BrowserSwitchResult.Success) { - Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchResult.Success) result).getResultInfo()); + Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchResult.Success) result)); } PendingRequestStore.Companion.clear(this); } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java index 249092e3..32aa1751 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java @@ -15,6 +15,7 @@ import com.braintreepayments.api.BrowserSwitchException; import com.braintreepayments.api.BrowserSwitchOptions; +import com.braintreepayments.api.BrowserSwitchResult; import com.braintreepayments.api.BrowserSwitchResultInfo; import org.json.JSONException; @@ -99,16 +100,14 @@ private void clearTextViews() { mMetadataTextView.setText(""); } - public void onBrowserSwitchResult(BrowserSwitchResultInfo result) { + public void onBrowserSwitchResult(BrowserSwitchResult.Success result) { String selectedColorText = ""; String resultText = "Browser Switch Successful"; Uri returnUrl = result.getDeepLinkUrl(); - if (returnUrl != null) { - String color = returnUrl.getQueryParameter("color"); - selectedColorText = String.format("Selected color: %s", color); - } + String color = returnUrl.getQueryParameter("color"); + selectedColorText = String.format("Selected color: %s", color); mBrowserSwitchStatusTextView.setText(resultText); mSelectedColorTextView.setText(selectedColorText); diff --git a/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt b/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt index 2c324851..12749aec 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt @@ -16,7 +16,7 @@ class PendingRequestStore { SHARED_PREFS_KEY, Context.MODE_PRIVATE ) - sharedPreferences.edit().putString(PENDING_REQUEST_KEY, pendingRequest.toJsonString()).apply() + sharedPreferences.edit().putString(PENDING_REQUEST_KEY, pendingRequest.token).apply() } fun get(context: Context) : BrowserSwitchPendingRequest.Started? { diff --git a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt index 2837f2fe..607f35c3 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt @@ -1,6 +1,7 @@ package com.braintreepayments.api.demo.viewmodel import androidx.lifecycle.ViewModel +import com.braintreepayments.api.BrowserSwitchResult import com.braintreepayments.api.BrowserSwitchResultInfo import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -12,7 +13,7 @@ class BrowserSwitchViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _uiState.asStateFlow() - var browserSwitchResult : BrowserSwitchResultInfo? + var browserSwitchResult : BrowserSwitchResult.Success? get() = _uiState.value.browserSwitchResult set(value) { _uiState.update { it.copy(browserSwitchResult = value) } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt index 987aff27..6e90bfc9 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt @@ -1,8 +1,8 @@ package com.braintreepayments.api.demo.viewmodel -import com.braintreepayments.api.BrowserSwitchResultInfo +import com.braintreepayments.api.BrowserSwitchResult -data class UiState ( - val browserSwitchResult: BrowserSwitchResultInfo? = null, +data class UiState( + val browserSwitchResult: BrowserSwitchResult.Success? = null, val browserSwitchError: Exception? = null, ) \ No newline at end of file From a66ed899fbc516371db2ddf03aa737db35d5c3d2 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 26 Mar 2024 13:04:57 -0500 Subject: [PATCH 02/13] Fix opt metadata bug. --- .../api/BrowserSwitchRequest.java | 5 +- .../api/BrowserSwitchResultInfo.java | 71 ------------------- .../api/BrowserSwitchResultInfoUnitTest.java | 38 ---------- .../api/demo/ComposeActivity.kt | 1 - .../api/demo/DemoFragment.java | 1 - .../demo/viewmodel/BrowserSwitchViewModel.kt | 1 - 6 files changed, 4 insertions(+), 113 deletions(-) delete mode 100644 browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java delete mode 100644 browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java index 415985f9..f04b7c0e 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java @@ -14,6 +14,9 @@ // Links // Base64 Encode a String in Android: https://stackoverflow.com/a/7360440 +// TODO: consider encryption +// Ref: https://medium.com/fw-engineering/sharedpreferences-and-android-keystore-c4eac3373ac7 + public class BrowserSwitchRequest { private static final String KEY_REQUEST_CODE = "requestCode"; @@ -37,7 +40,7 @@ static BrowserSwitchRequest fromToken(@NonNull String tokenBase64) throws Browse return new BrowserSwitchRequest( tokenJSON.getInt(KEY_REQUEST_CODE), Uri.parse(tokenJSON.getString(KEY_URL)), - tokenJSON.getJSONObject(KEY_METADATA), + tokenJSON.optJSONObject(KEY_METADATA), tokenJSON.getString(KEY_RETURN_URL_SCHEME) ); } catch (JSONException e) { diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java deleted file mode 100644 index eec5adc6..00000000 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResultInfo.java +++ /dev/null @@ -1,71 +0,0 @@ -package com.braintreepayments.api; - -import android.net.Uri; - -import androidx.annotation.Nullable; - -import org.json.JSONException; -import org.json.JSONObject; - -/** - * Details of a successful {@link BrowserSwitchResult} - */ -public class BrowserSwitchResultInfo { - - private static final String KEY_DEEP_LINK_URL = "deepLinkUrl"; - private static final String KEY_BROWSER_SWITCH_REQUEST = "browserSwitchRequest"; - - private final Uri deepLinkUrl; - private final BrowserSwitchRequest request; - - static BrowserSwitchResultInfo fromJson(String json) throws JSONException { - JSONObject jsonObject = new JSONObject(json); - String deepLinkUrl = jsonObject.getString(KEY_DEEP_LINK_URL); - String browserSwitchRequest = jsonObject.getString(KEY_BROWSER_SWITCH_REQUEST); - return null; -// return new BrowserSwitchResultInfo(BrowserSwitchRequest.fromJson(browserSwitchRequest), Uri.parse(deepLinkUrl)); - } - - BrowserSwitchResultInfo(BrowserSwitchRequest request, Uri deepLinkUrl) { - this.request = request; - this.deepLinkUrl = deepLinkUrl; - } - - /** - * @return A {@link JSONObject} containing metadata persisted through the browser switch - */ - @Nullable - public JSONObject getRequestMetadata() { - return request.getMetadata(); - } - - /** - * @return Request code int to associate with the browser switch request - */ - public int getRequestCode() { - return request.getRequestCode(); - } - - /** - * @return The target url used to initiate the browser switch - */ - @Nullable - public Uri getRequestUrl() { - return request.getUrl(); - } - - /** - * @return The return url used for deep linking back into the application after browser switch - */ - @Nullable - public Uri getDeepLinkUrl() { - return deepLinkUrl; - } - - public String toJson() throws JSONException { - JSONObject result = new JSONObject(); - result.put(KEY_DEEP_LINK_URL, deepLinkUrl.toString()); -// result.put(KEY_BROWSER_SWITCH_REQUEST, request.toJson()); - return result.toString(); - } -} diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java deleted file mode 100644 index 9d00cc04..00000000 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchResultInfoUnitTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.braintreepayments.api; - -import static org.junit.Assert.assertEquals; - -import android.net.Uri; - -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.skyscreamer.jsonassert.JSONAssert; - -@RunWith(RobolectricTestRunner.class) -public class BrowserSwitchResultInfoUnitTest { - - @Test - public void toJSON_serializesResult() throws JSONException { - Uri requestUrl = Uri.parse("https://www.example.com"); - String returnUrlScheme = "example.return.url.scheme"; - JSONObject requestMetadata = new JSONObject() - .put("sample", "value"); - BrowserSwitchRequest request = - new BrowserSwitchRequest(123, requestUrl, requestMetadata, returnUrlScheme); - - Uri deepLinkUrl = Uri.parse("example.return.url.scheme://success/ok"); - BrowserSwitchResultInfo - sut = new BrowserSwitchResultInfo(request, deepLinkUrl); - - BrowserSwitchResultInfo sutSerialized = BrowserSwitchResultInfo.fromJson(sut.toJson()); - - assertEquals(deepLinkUrl, sutSerialized.getDeepLinkUrl()); - - assertEquals(123, sutSerialized.getRequestCode()); - assertEquals(requestUrl, sutSerialized.getRequestUrl()); - JSONAssert.assertEquals(requestMetadata, sutSerialized.getRequestMetadata(), true); - } -} diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index 04676874..5622d478 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -21,7 +21,6 @@ import com.braintreepayments.api.BrowserSwitchClient import com.braintreepayments.api.BrowserSwitchOptions import com.braintreepayments.api.BrowserSwitchPendingRequest import com.braintreepayments.api.BrowserSwitchResult -import com.braintreepayments.api.BrowserSwitchResultInfo import com.braintreepayments.api.demo.utils.PendingRequestStore import com.braintreepayments.api.demo.viewmodel.BrowserSwitchViewModel import org.json.JSONObject diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java index 32aa1751..d682bfe5 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java @@ -16,7 +16,6 @@ import com.braintreepayments.api.BrowserSwitchException; import com.braintreepayments.api.BrowserSwitchOptions; import com.braintreepayments.api.BrowserSwitchResult; -import com.braintreepayments.api.BrowserSwitchResultInfo; import org.json.JSONException; import org.json.JSONObject; diff --git a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt index 607f35c3..97c3d5fe 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt @@ -2,7 +2,6 @@ package com.braintreepayments.api.demo.viewmodel import androidx.lifecycle.ViewModel import com.braintreepayments.api.BrowserSwitchResult -import com.braintreepayments.api.BrowserSwitchResultInfo import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow From bacf539fbdefa7dbb3f66b2bae173f3b1438dd43 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 26 Mar 2024 15:04:03 -0500 Subject: [PATCH 03/13] Update tests and use token externally. --- .../api/BrowserSwitchClient.java | 14 +- .../api/BrowserSwitchOptions.java | 2 +- .../api/BrowserSwitchClientUnitTest.java | 29 ++-- .../BrowserSwitchPendingRequestUnitTest.kt | 52 ------- .../api/BrowserSwitchRequestUnitTest.java | 143 ------------------ .../api/BrowserSwitchRequestUnitTest.kt | 29 ++++ .../api/demo/ComposeActivity.kt | 4 +- .../api/demo/DemoActivitySingleTop.java | 19 +-- .../api/demo/utils/PendingRequestStore.kt | 15 +- 9 files changed, 73 insertions(+), 234 deletions(-) delete mode 100644 browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt delete mode 100644 browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.java create mode 100644 browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt 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 8636fa71..16fa1cc2 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -45,7 +45,7 @@ public BrowserSwitchClient() { * @param activity the activity used to start browser switch * @param browserSwitchOptions {@link BrowserSwitchOptions} the options used to configure the browser switch * @return a {@link BrowserSwitchPendingRequest.Started} that should be stored and passed to - * {@link BrowserSwitchClient#parseResult(BrowserSwitchPendingRequest.Started, Intent)} upon return to the app, + * {@link BrowserSwitchClient#parseResult(String, Intent)} upon return to the app, * or {@link BrowserSwitchPendingRequest.Failure} if browser could not be launched. */ @NonNull @@ -107,21 +107,21 @@ private boolean isValidRequestCode(int requestCode) { /** * Parses and returns a browser switch result if a match is found for the given {@link BrowserSwitchRequest} * - * @param pendingRequest the {@link BrowserSwitchPendingRequest.Started} returned from - * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} - * @param intent the intent to return to your application containing a deep link result from the - * browser flow + * @param pendingRequestToken the {@link BrowserSwitchPendingRequest.Started} returned from + * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} + * @param intent the intent to return to your application containing a deep link result from the + * browser flow * @return a {@link BrowserSwitchResult.Success} if the browser switch was successfully * completed, or {@link BrowserSwitchResult.NoResult} if no result can be found for the given * {@link BrowserSwitchPendingRequest.Started}. A {@link BrowserSwitchResult.NoResult} will be * returned if the user returns to the app without completing the browser switch flow. */ - public BrowserSwitchResult parseResult(@NonNull BrowserSwitchPendingRequest.Started pendingRequest, @Nullable Intent intent) { + public BrowserSwitchResult parseResult(@NonNull String pendingRequestToken, @Nullable Intent intent) { if (intent != null && intent.getData() != null) { Uri deepLinkUrl = intent.getData(); try { BrowserSwitchRequest originalRequest = - BrowserSwitchRequest.fromToken(pendingRequest.getToken()); + BrowserSwitchRequest.fromToken(pendingRequestToken); if (originalRequest.matchesDeepLinkUrlScheme(deepLinkUrl)) { return new BrowserSwitchResult.Success(deepLinkUrl, originalRequest); } diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java index ff498519..19d1f53a 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java @@ -24,7 +24,7 @@ public class BrowserSwitchOptions { * Set browser switch metadata. * * @param metadata JSONObject containing metadata that will be persisted and returned in a - * {@link BrowserSwitchResultInfo} when the app has re-entered the foreground + * {@link BrowserSwitchResult} when the app has re-entered the foreground * @return {@link BrowserSwitchOptions} reference to instance to allow setter invocations to be chained */ public BrowserSwitchOptions metadata(@Nullable JSONObject metadata) { 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 12217859..d7bbf074 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -2,7 +2,6 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; @@ -19,6 +18,7 @@ import androidx.activity.ComponentActivity; +import org.json.JSONException; import org.json.JSONObject; import org.junit.Before; import org.junit.Test; @@ -26,6 +26,7 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.android.controller.ActivityController; +import org.skyscreamer.jsonassert.JSONAssert; @RunWith(RobolectricTestRunner.class) public class BrowserSwitchClientUnitTest { @@ -75,7 +76,7 @@ public void start_whenActivityIsFinishing_throwsException() { } @Test - public void start_whenSuccessful_returnsBrowserSwitchRequest() { + public void start_whenSuccessful_returnsBrowserSwitchRequest() throws BrowserSwitchException, JSONException { when(browserSwitchInspector.isDeviceConfiguredForDeepLinking(applicationContext, "return-url-scheme")).thenReturn(true); BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, @@ -94,11 +95,11 @@ public void start_whenSuccessful_returnsBrowserSwitchRequest() { assertNotNull(browserSwitchPendingRequest); assertTrue(browserSwitchPendingRequest instanceof BrowserSwitchPendingRequest.Started); - BrowserSwitchRequest browserSwitchRequest = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getToken(); + String token = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getToken(); + BrowserSwitchRequest browserSwitchRequest = BrowserSwitchRequest.fromToken(token); assertEquals(browserSwitchRequest.getRequestCode(), 123); assertEquals(browserSwitchRequest.getUrl(), browserSwitchDestinationUrl); - assertSame(browserSwitchRequest.getMetadata(), metadata); - assertTrue(browserSwitchRequest.getShouldNotifyCancellation()); + JSONAssert.assertEquals(metadata, browserSwitchRequest.getMetadata(), false); } @Test @@ -119,6 +120,7 @@ public void start_whenNoBrowserAvailable_returnsFailure() { assertTrue(request instanceof BrowserSwitchPendingRequest.Failure); assertEquals(((BrowserSwitchPendingRequest.Failure) request).getCause().getMessage(), "Unable to start browser switch without a web browser."); } + @Test public void start_whenRequestCodeIsIntegerMinValue_returnsFailure() { when(browserSwitchInspector.isDeviceConfiguredForDeepLinking(applicationContext, "return-url-scheme")).thenReturn(true); @@ -178,24 +180,25 @@ public void start_whenNoReturnUrlSchemeSet_throwsFailure() { } @Test - public void parseResult_whenActiveRequestMatchesDeepLinkResultURLScheme_returnsBrowserSwitchSuccessResult() { + public void parseResult_whenActiveRequestMatchesDeepLinkResultURLScheme_returnsBrowserSwitchSuccessResult() throws BrowserSwitchException { BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, customTabsInternalClient); JSONObject requestMetadata = new JSONObject(); BrowserSwitchRequest request = - new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); + new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); Uri deepLinkUrl = Uri.parse("fake-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); - BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), intent); + + BrowserSwitchResult browserSwitchResult = sut.parseResult(request.tokenize(), intent); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.Success); - assertEquals(deepLinkUrl, ((BrowserSwitchResult.Success) browserSwitchResult).getResultInfo().getDeepLinkUrl()); + assertEquals(deepLinkUrl, ((BrowserSwitchResult.Success) browserSwitchResult).getDeepLinkUrl()); } @Test - public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() { + public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() throws BrowserSwitchException { BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, customTabsInternalClient); @@ -205,13 +208,13 @@ public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() Uri deepLinkUrl = Uri.parse("a-different-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); - BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), intent); + BrowserSwitchResult browserSwitchResult = sut.parseResult(request.tokenize(), intent); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); } @Test - public void parseResult_whenIntentIsNull_returnsNoResult() { + public void parseResult_whenIntentIsNull_returnsNoResult() throws BrowserSwitchException { BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector, customTabsInternalClient); @@ -219,7 +222,7 @@ public void parseResult_whenIntentIsNull_returnsNoResult() { BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); - BrowserSwitchResult browserSwitchResult = sut.parseResult(new BrowserSwitchPendingRequest.Started(request), null); + BrowserSwitchResult browserSwitchResult = sut.parseResult(request.tokenize(), null); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); } } diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt deleted file mode 100644 index 4baa07a0..00000000 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchPendingRequestUnitTest.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.braintreepayments.api - -import android.net.Uri -import org.json.JSONException -import org.json.JSONObject -import org.junit.Assert.assertEquals -import org.junit.Test -import org.junit.runner.RunWith -import org.robolectric.RobolectricTestRunner - -@RunWith(RobolectricTestRunner::class) -class BrowserSwitchPendingRequestUnitTest { - - private val browserSwitchRequest = BrowserSwitchRequest( - 1, - Uri.parse("http://"), - JSONObject().put("test_key", "test_value"), - "return-url-scheme" - ) - - @Test - fun startedConstructor_fromString_createsBrowserSwitchRequest() { - val pendingRequest = BrowserSwitchPendingRequest.Started(browserSwitchRequest) - val storedRequest = pendingRequest.toJsonString() - - val sut = BrowserSwitchPendingRequest.Started(storedRequest) - assertEquals(browserSwitchRequest.requestCode, sut.token.requestCode) - assertEquals( - browserSwitchRequest.metadata.getString("test_key"), - sut.token.metadata.getString("test_key") - ) - assertEquals(browserSwitchRequest.url, sut.token.url) - assertEquals( - browserSwitchRequest.shouldNotifyCancellation, - sut.token.shouldNotifyCancellation - ) - assertEquals(browserSwitchRequest.returnUrlScheme, sut.token.returnUrlScheme) - } - - @Test(expected = JSONException::class) - fun startedConstructor_fromString_whenInvalidString_throwsJSONException() { - val sut = BrowserSwitchPendingRequest.Started("{}") - } - - @Test - fun toJsonString_returnsJsonBrowserSwitchRequest() { - val sut = BrowserSwitchPendingRequest.Started(browserSwitchRequest) - val jsonString = sut.toJsonString() - - assertEquals(browserSwitchRequest.toJson(), jsonString) - } -} \ No newline at end of file diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.java b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.java deleted file mode 100644 index 6adf5887..00000000 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.braintreepayments.api; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.net.Uri; - -import org.json.JSONException; -import org.json.JSONObject; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; -import org.skyscreamer.jsonassert.JSONAssert; - -@RunWith(RobolectricTestRunner.class) -public class BrowserSwitchRequestUnitTest { - - @Test - public void fromJson_withoutShouldNotifyProperty_defaultsShouldNotifyToTrue() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\"\n" + - "}"; - BrowserSwitchRequest sut = BrowserSwitchRequest.fromJson(json); - assertTrue(sut.getShouldNotifyCancellation()); - } - - @Test - public void fromJson_withoutMetadata() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\",\n" + - " \"shouldNotify\": true\n" + - "}"; - BrowserSwitchRequest sut = BrowserSwitchRequest.fromJson(json); - - assertEquals(sut.getRequestCode(), 123); - assertEquals(sut.getUrl().toString(), "https://example.com"); - assertNull(sut.getMetadata()); - assertTrue(sut.getShouldNotifyCancellation()); - - assertTrue(sut.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test"))); - assertFalse(sut.matchesDeepLinkUrlScheme(Uri.parse("another-return-url-scheme://test"))); - } - - @Test - public void toJson_withoutMetadata() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\",\n" + - " \"shouldNotify\": false\n" + - "}"; - BrowserSwitchRequest original = BrowserSwitchRequest.fromJson(json); - BrowserSwitchRequest restored = BrowserSwitchRequest.fromJson(original.toJson()); - - assertEquals(restored.getRequestCode(), original.getRequestCode()); - assertEquals(restored.getUrl(), original.getUrl()); - assertNull(restored.getMetadata()); - assertFalse(restored.getShouldNotifyCancellation()); - - assertTrue(restored.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test"))); - assertFalse(restored.matchesDeepLinkUrlScheme(Uri.parse("another-return-url-scheme://test"))); - } - - @Test - public void fromJson_withMetadata() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\",\n" + - " \"shouldNotify\": true,\n" + - " \"metadata\": {\n" + - " \"testKey\": \"testValue\"" + - " }" + - "}"; - BrowserSwitchRequest sut = BrowserSwitchRequest.fromJson(json); - - assertEquals(sut.getRequestCode(), 123); - assertEquals(sut.getUrl().toString(), "https://example.com"); - assertTrue(sut.getShouldNotifyCancellation()); - JSONAssert.assertEquals(sut.getMetadata(), new JSONObject().put("testKey", "testValue"), true); - - assertTrue(sut.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test"))); - assertFalse(sut.matchesDeepLinkUrlScheme(Uri.parse("another-return-url-scheme://test"))); - } - - @Test - public void toJson_withMetadata() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\",\n" + - " \"shouldNotify\": false,\n" + - " \"metadata\": {\n" + - " \"testKey\": \"testValue\"" + - " }" + - "}"; - BrowserSwitchRequest original = BrowserSwitchRequest.fromJson(json); - BrowserSwitchRequest restored = BrowserSwitchRequest.fromJson(original.toJson()); - - assertEquals(restored.getRequestCode(), original.getRequestCode()); - assertEquals(restored.getUrl(), original.getUrl()); - assertFalse(restored.getShouldNotifyCancellation()); - JSONAssert.assertEquals(restored.getMetadata(), original.getMetadata(), true); - - assertTrue(restored.matchesDeepLinkUrlScheme(Uri.parse("my-return-url-scheme://test"))); - assertFalse(restored.matchesDeepLinkUrlScheme(Uri.parse("another-return-url-scheme://test"))); - } - - @Test - public void matchesDeepLinkUrlScheme_whenSameSchemeDifferentCase_returnsTrue() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\",\n" + - " \"shouldNotify\": false,\n" + - " \"metadata\": {\n" + - " \"testKey\": \"testValue\"" + - " }" + - "}"; - BrowserSwitchRequest request = BrowserSwitchRequest.fromJson(json); - assertTrue(request.matchesDeepLinkUrlScheme(Uri.parse("My-Return-Url-Scheme://example.com"))); - } - @Test - public void matchesDeepLinkUrlScheme_whenDifferentScheme_returnsFalse() throws JSONException { - String json = "{\n" + - " \"requestCode\": 123,\n" + - " \"url\": \"https://example.com\",\n" + - " \"returnUrlScheme\": \"my-return-url-scheme\",\n" + - " \"shouldNotify\": false,\n" + - " \"metadata\": {\n" + - " \"testKey\": \"testValue\"" + - " }" + - "}"; - BrowserSwitchRequest request = BrowserSwitchRequest.fromJson(json); - assertFalse(request.matchesDeepLinkUrlScheme(Uri.parse("not-my-return-url-scheme://example.com"))); - } -} diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt new file mode 100644 index 00000000..077d1e2b --- /dev/null +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt @@ -0,0 +1,29 @@ +package com.braintreepayments.api + +import android.net.Uri +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class BrowserSwitchRequestUnitTest { + + @Test + fun startedConstructor_fromString_createsBrowserSwitchRequest() { + val browserSwitchRequest = BrowserSwitchRequest( + 1, + Uri.parse("http://"), + JSONObject().put("test_key", "test_value"), + "return-url-scheme" + ) + + val token = browserSwitchRequest.tokenize() + val sut = BrowserSwitchRequest.fromToken(token) + assertEquals(browserSwitchRequest.requestCode, sut.requestCode) + assertEquals("test_value", sut.metadata.getString("test_key")) + assertEquals(Uri.parse("http://"), sut.url) + assertEquals("return-url-scheme", sut.returnUrlScheme) + } +} \ No newline at end of file diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index 5622d478..5754611b 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -75,7 +75,9 @@ class ComposeActivity : ComponentActivity() { .launchAsNewTask(false) .returnUrlScheme(RETURN_URL_SCHEME) when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { - is BrowserSwitchPendingRequest.Started -> PendingRequestStore.put(this, pendingRequest) + is BrowserSwitchPendingRequest.Started -> + PendingRequestStore.put(this, pendingRequest.token) + is BrowserSwitchPendingRequest.Failure -> viewModel.browserSwitchError = pendingRequest.cause } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java index f841ead0..edf6da9f 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java @@ -43,13 +43,13 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - BrowserSwitchPendingRequest.Started pendingRequest = PendingRequestStore.Companion.get(this); - if (pendingRequest != null) { - BrowserSwitchResult result = browserSwitchClient.parseResult(pendingRequest, intent); + String pendingRequestToken = PendingRequestStore.get(this); + if (pendingRequestToken != null) { + BrowserSwitchResult result = browserSwitchClient.parseResult(pendingRequestToken, intent); if (result instanceof BrowserSwitchResult.Success) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchResult.Success) result)); } - PendingRequestStore.Companion.clear(this); + PendingRequestStore.clear(this); } } @@ -57,18 +57,19 @@ protected void onNewIntent(Intent intent) { protected void onResume() { super.onResume(); - BrowserSwitchPendingRequest.Started pendingRequest = PendingRequestStore.Companion.get(this); - if (pendingRequest != null) { + String pendingRequestToken = PendingRequestStore.get(this); + if (pendingRequestToken != null) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(new Exception("User did not complete browser switch")); - PendingRequestStore.Companion.clear(this); + PendingRequestStore.clear(this); } } public void startBrowserSwitch(BrowserSwitchOptions options) throws BrowserSwitchException { BrowserSwitchPendingRequest pendingRequest = browserSwitchClient.start(this, options); if (pendingRequest instanceof BrowserSwitchPendingRequest.Started) { - PendingRequestStore.Companion.put(this, - (BrowserSwitchPendingRequest.Started) pendingRequest); + String pendingRequestToken = + ((BrowserSwitchPendingRequest.Started) pendingRequest).getToken(); + PendingRequestStore.put(this, pendingRequestToken); } else if (pendingRequest instanceof BrowserSwitchPendingRequest.Failure) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchPendingRequest.Failure) pendingRequest).getCause()); } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt b/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt index 12749aec..9c842b8c 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt @@ -11,26 +11,25 @@ class PendingRequestStore { private const val SHARED_PREFS_KEY = "PENDING_REQUESTS" private const val PENDING_REQUEST_KEY = "BROWSER_SWITCH_REQUEST" - fun put(context: Context, pendingRequest: BrowserSwitchPendingRequest.Started) { + @JvmStatic + fun put(context: Context, pendingRequestToken: String) { val sharedPreferences: SharedPreferences = context.getSharedPreferences( SHARED_PREFS_KEY, Context.MODE_PRIVATE ) - sharedPreferences.edit().putString(PENDING_REQUEST_KEY, pendingRequest.token).apply() + sharedPreferences.edit().putString(PENDING_REQUEST_KEY, pendingRequestToken).apply() } - fun get(context: Context) : BrowserSwitchPendingRequest.Started? { + @JvmStatic + fun get(context: Context): String? { val sharedPreferences: SharedPreferences = context.getSharedPreferences( SHARED_PREFS_KEY, Context.MODE_PRIVATE ) - val pendingRequestString = sharedPreferences.getString(PENDING_REQUEST_KEY, null) - pendingRequestString?.let { - return BrowserSwitchPendingRequest.Started(it) - } - return null + return sharedPreferences.getString(PENDING_REQUEST_KEY, null) } + @JvmStatic fun clear(context: Context) { val sharedPreferences: SharedPreferences = context.getSharedPreferences( SHARED_PREFS_KEY, From e5c67b246b7f073e5f09027760c4b47ea16628b3 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Mon, 1 Apr 2024 11:40:05 -0500 Subject: [PATCH 04/13] Add error state to browser switching. --- .../java/com/braintreepayments/api/BrowserSwitchClient.java | 3 +-- .../java/com/braintreepayments/api/BrowserSwitchResult.kt | 6 ++++++ 2 files changed, 7 insertions(+), 2 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 16fa1cc2..7c206ca8 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -126,8 +126,7 @@ public BrowserSwitchResult parseResult(@NonNull String pendingRequestToken, @Nul return new BrowserSwitchResult.Success(deepLinkUrl, originalRequest); } } catch (BrowserSwitchException e) { - // TODO: handle error - throw new RuntimeException(e); + return new BrowserSwitchResult.UnknownError(e); } } return BrowserSwitchResult.NoResult.INSTANCE; diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt index a8fe226e..ae66a200 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt @@ -31,4 +31,10 @@ sealed class BrowserSwitchResult { * without completing the browser switch flow. */ object NoResult : BrowserSwitchResult() + + /** + * The browser switch failed. + * @property [reason] Reason for the browser switch failure. + */ + class Failure(val reason: BrowserSwitchException) : BrowserSwitchResult() } From c52bb8e12677f623d54661b6d17eda1251870494 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 09:38:50 -0500 Subject: [PATCH 05/13] Add Failure type to BrowserSwitchResult parser. --- .../java/com/braintreepayments/api/BrowserSwitchClient.java | 2 +- .../java/com/braintreepayments/api/BrowserSwitchResult.kt | 6 +++--- 2 files changed, 4 insertions(+), 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 7c206ca8..bb1e30fc 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -126,7 +126,7 @@ public BrowserSwitchResult parseResult(@NonNull String pendingRequestToken, @Nul return new BrowserSwitchResult.Success(deepLinkUrl, originalRequest); } } catch (BrowserSwitchException e) { - return new BrowserSwitchResult.UnknownError(e); + return new BrowserSwitchResult.Failure(e); } } return BrowserSwitchResult.NoResult.INSTANCE; diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt index ae66a200..099d5a59 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt @@ -11,13 +11,13 @@ sealed class BrowserSwitchResult { /** * The browser switch was successfully completed. See [resultInfo] for details. */ - class Success( + class Success internal constructor( val deepLinkUrl: Uri, val requestCode: Int, val requestUrl: Uri, val requestMetadata: JSONObject?, ) : BrowserSwitchResult() { - constructor(deepLinkUrl: Uri, originalRequest: BrowserSwitchRequest) : this( + internal constructor(deepLinkUrl: Uri, originalRequest: BrowserSwitchRequest) : this( deepLinkUrl, originalRequest.requestCode, originalRequest.url, @@ -36,5 +36,5 @@ sealed class BrowserSwitchResult { * The browser switch failed. * @property [reason] Reason for the browser switch failure. */ - class Failure(val reason: BrowserSwitchException) : BrowserSwitchResult() + class Failure internal constructor(val reason: BrowserSwitchException) : BrowserSwitchResult() } From 0e2b07dadcc6a7ebbecbe5a4e39c942a8ef9f992 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 10:03:13 -0500 Subject: [PATCH 06/13] Refactor naming. --- .../api/BrowserSwitchClient.java | 14 ++++----- .../api/BrowserSwitchPendingRequest.kt | 2 +- .../api/BrowserSwitchRequest.java | 31 +++++++++---------- .../api/BrowserSwitchClientUnitTest.java | 10 +++--- .../api/BrowserSwitchRequestUnitTest.kt | 4 +-- .../api/demo/ComposeActivity.kt | 8 ++--- .../api/demo/DemoActivitySingleTop.java | 16 +++++----- .../api/demo/utils/PendingRequestStore.kt | 5 ++- 8 files changed, 43 insertions(+), 47 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 bb1e30fc..02394887 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -7,12 +7,10 @@ import androidx.activity.ComponentActivity; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.braintreepayments.api.browserswitch.R; -import org.json.JSONException; import org.json.JSONObject; /** @@ -45,7 +43,7 @@ public BrowserSwitchClient() { * @param activity the activity used to start browser switch * @param browserSwitchOptions {@link BrowserSwitchOptions} the options used to configure the browser switch * @return a {@link BrowserSwitchPendingRequest.Started} that should be stored and passed to - * {@link BrowserSwitchClient#parseResult(String, Intent)} upon return to the app, + * {@link BrowserSwitchClient#parseResult(Intent, String)} upon return to the app, * or {@link BrowserSwitchPendingRequest.Failure} if browser could not be launched. */ @NonNull @@ -72,7 +70,7 @@ public BrowserSwitchPendingRequest start(@NonNull ComponentActivity activity, @N BrowserSwitchRequest request = new BrowserSwitchRequest(requestCode, browserSwitchUrl, metadata, returnUrlScheme); customTabsInternalClient.launchUrl(activity, browserSwitchUrl, launchAsNewTask); - return new BrowserSwitchPendingRequest.Started(request.tokenize()); + return new BrowserSwitchPendingRequest.Started(request.toBase64EncodedJSON()); } catch (ActivityNotFoundException | BrowserSwitchException e) { return new BrowserSwitchPendingRequest.Failure(new BrowserSwitchException("Unable to start browser switch without a web browser.", e)); } @@ -107,21 +105,21 @@ private boolean isValidRequestCode(int requestCode) { /** * Parses and returns a browser switch result if a match is found for the given {@link BrowserSwitchRequest} * - * @param pendingRequestToken the {@link BrowserSwitchPendingRequest.Started} returned from - * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} * @param intent the intent to return to your application containing a deep link result from the * browser flow + * @param pendingRequestState the {@link BrowserSwitchPendingRequest.Started} token returned from + * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} * @return a {@link BrowserSwitchResult.Success} if the browser switch was successfully * completed, or {@link BrowserSwitchResult.NoResult} if no result can be found for the given * {@link BrowserSwitchPendingRequest.Started}. A {@link BrowserSwitchResult.NoResult} will be * returned if the user returns to the app without completing the browser switch flow. */ - public BrowserSwitchResult parseResult(@NonNull String pendingRequestToken, @Nullable Intent intent) { + public BrowserSwitchResult parseResult(@NonNull Intent intent, @NonNull String pendingRequestState) { if (intent != null && intent.getData() != null) { Uri deepLinkUrl = intent.getData(); try { BrowserSwitchRequest originalRequest = - BrowserSwitchRequest.fromToken(pendingRequestToken); + BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestState); if (originalRequest.matchesDeepLinkUrlScheme(deepLinkUrl)) { return new BrowserSwitchResult.Success(deepLinkUrl, originalRequest); } diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt index e3bfe110..8a603844 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt @@ -10,7 +10,7 @@ sealed class BrowserSwitchPendingRequest { * A browser switch was successfully started. This pending request should be store dnd passed to * [BrowserSwitchClient.parseResult] */ - class Started(val token: String) : BrowserSwitchPendingRequest() + class Started(val pendingRequestState: String) : BrowserSwitchPendingRequest() /** * An error with [cause] occurred launching the browser diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java index f04b7c0e..90b22f2c 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java @@ -31,20 +31,19 @@ public class BrowserSwitchRequest { final String returnUrlScheme; @NonNull - static BrowserSwitchRequest fromToken(@NonNull String tokenBase64) throws BrowserSwitchException { - byte[] data = Base64.decode(tokenBase64, Base64.DEFAULT); - String token = new String(data, StandardCharsets.UTF_8); - + static BrowserSwitchRequest fromBase64EncodedJSON(@NonNull String base64EncodedRequest) throws BrowserSwitchException { + byte[] data = Base64.decode(base64EncodedRequest, Base64.DEFAULT); + String requestJSONString = new String(data, StandardCharsets.UTF_8); try { - JSONObject tokenJSON = new JSONObject(token); + JSONObject requestJSON = new JSONObject(requestJSONString); return new BrowserSwitchRequest( - tokenJSON.getInt(KEY_REQUEST_CODE), - Uri.parse(tokenJSON.getString(KEY_URL)), - tokenJSON.optJSONObject(KEY_METADATA), - tokenJSON.getString(KEY_RETURN_URL_SCHEME) + requestJSON.getInt(KEY_REQUEST_CODE), + Uri.parse(requestJSON.getString(KEY_URL)), + requestJSON.optJSONObject(KEY_METADATA), + requestJSON.getString(KEY_RETURN_URL_SCHEME) ); } catch (JSONException e) { - throw new BrowserSwitchException("Unable to decode browser switch state from token.", e); + throw new BrowserSwitchException("Unable to deserialize browser switch state.", e); } } @@ -52,6 +51,7 @@ static BrowserSwitchRequest fromToken(@NonNull String tokenBase64) throws Browse this.url = url; this.requestCode = requestCode; this.metadata = metadata; + // TODO: make return url scheme accessor public this.returnUrlScheme = returnUrlScheme; } @@ -68,19 +68,18 @@ JSONObject getMetadata() { } @NonNull - String tokenize() throws BrowserSwitchException { + String toBase64EncodedJSON() throws BrowserSwitchException { try { - // TODO: make return url scheme accessor public - JSONObject tokenJSON = new JSONObject() + JSONObject requestJSON = new JSONObject() .put(KEY_REQUEST_CODE, requestCode) .put(KEY_URL, url.toString()) .put(KEY_RETURN_URL_SCHEME, returnUrlScheme) .putOpt(KEY_METADATA, metadata); - byte[] tokenBytes = tokenJSON.toString().getBytes(StandardCharsets.UTF_8); - return Base64.encodeToString(tokenBytes, Base64.DEFAULT); + byte[] requestJSONBytes = requestJSON.toString().getBytes(StandardCharsets.UTF_8); + return Base64.encodeToString(requestJSONBytes, Base64.DEFAULT); } catch (JSONException e) { - throw new BrowserSwitchException("Unable to tokenize Browser Switch State", e); + throw new BrowserSwitchException("Unable to serialize browser switch state.", e); } } 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 d7bbf074..aba1c301 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -95,8 +95,8 @@ public void start_whenSuccessful_returnsBrowserSwitchRequest() throws BrowserSwi assertNotNull(browserSwitchPendingRequest); assertTrue(browserSwitchPendingRequest instanceof BrowserSwitchPendingRequest.Started); - String token = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getToken(); - BrowserSwitchRequest browserSwitchRequest = BrowserSwitchRequest.fromToken(token); + String pendingRequestState = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getPendingRequestState(); + BrowserSwitchRequest browserSwitchRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestState); assertEquals(browserSwitchRequest.getRequestCode(), 123); assertEquals(browserSwitchRequest.getUrl(), browserSwitchDestinationUrl); JSONAssert.assertEquals(metadata, browserSwitchRequest.getMetadata(), false); @@ -191,7 +191,7 @@ public void parseResult_whenActiveRequestMatchesDeepLinkResultURLScheme_returnsB Uri deepLinkUrl = Uri.parse("fake-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); - BrowserSwitchResult browserSwitchResult = sut.parseResult(request.tokenize(), intent); + BrowserSwitchResult browserSwitchResult = sut.parseResult(intent, request.toBase64EncodedJSON()); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.Success); assertEquals(deepLinkUrl, ((BrowserSwitchResult.Success) browserSwitchResult).getDeepLinkUrl()); @@ -208,7 +208,7 @@ public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() Uri deepLinkUrl = Uri.parse("a-different-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); - BrowserSwitchResult browserSwitchResult = sut.parseResult(request.tokenize(), intent); + BrowserSwitchResult browserSwitchResult = sut.parseResult(intent, request.toBase64EncodedJSON()); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); } @@ -222,7 +222,7 @@ public void parseResult_whenIntentIsNull_returnsNoResult() throws BrowserSwitchE BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); - BrowserSwitchResult browserSwitchResult = sut.parseResult(request.tokenize(), null); + BrowserSwitchResult browserSwitchResult = sut.parseResult(null, request.toBase64EncodedJSON()); assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); } } diff --git a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt index 077d1e2b..e94f336a 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchRequestUnitTest.kt @@ -19,8 +19,8 @@ class BrowserSwitchRequestUnitTest { "return-url-scheme" ) - val token = browserSwitchRequest.tokenize() - val sut = BrowserSwitchRequest.fromToken(token) + val token = browserSwitchRequest.toBase64EncodedJSON() + val sut = BrowserSwitchRequest.fromBase64EncodedJSON(token) assertEquals(browserSwitchRequest.requestCode, sut.requestCode) assertEquals("test_value", sut.metadata.getString("test_key")) assertEquals(Uri.parse("http://"), sut.url) diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index 5754611b..948b46e4 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -47,9 +47,9 @@ class ComposeActivity : ComponentActivity() { override fun onResume() { super.onResume() - val pendingRequestToken = PendingRequestStore.get(this) - val browserSwitchResult = pendingRequestToken?.let { - browserSwitchClient.parseResult(pendingRequestToken, intent) + val pendingRequestState = PendingRequestStore.get(this) + val browserSwitchResult = pendingRequestState?.let { + browserSwitchClient.parseResult(intent, pendingRequestState) } when (browserSwitchResult) { is BrowserSwitchResult.Success -> { @@ -76,7 +76,7 @@ class ComposeActivity : ComponentActivity() { .returnUrlScheme(RETURN_URL_SCHEME) when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { is BrowserSwitchPendingRequest.Started -> - PendingRequestStore.put(this, pendingRequest.token) + PendingRequestStore.put(this, pendingRequest.pendingRequestState) is BrowserSwitchPendingRequest.Failure -> viewModel.browserSwitchError = pendingRequest.cause diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java index edf6da9f..0560de88 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java @@ -43,9 +43,9 @@ protected void onCreate(@Nullable Bundle savedInstanceState) { protected void onNewIntent(Intent intent) { super.onNewIntent(intent); - String pendingRequestToken = PendingRequestStore.get(this); - if (pendingRequestToken != null) { - BrowserSwitchResult result = browserSwitchClient.parseResult(pendingRequestToken, intent); + String pendingRequestState = PendingRequestStore.get(this); + if (pendingRequestState != null) { + BrowserSwitchResult result = browserSwitchClient.parseResult(intent, pendingRequestState); if (result instanceof BrowserSwitchResult.Success) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchResult.Success) result)); } @@ -57,8 +57,8 @@ protected void onNewIntent(Intent intent) { protected void onResume() { super.onResume(); - String pendingRequestToken = PendingRequestStore.get(this); - if (pendingRequestToken != null) { + String pendingRequestState = PendingRequestStore.get(this); + if (pendingRequestState != null) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(new Exception("User did not complete browser switch")); PendingRequestStore.clear(this); } @@ -67,9 +67,9 @@ protected void onResume() { public void startBrowserSwitch(BrowserSwitchOptions options) throws BrowserSwitchException { BrowserSwitchPendingRequest pendingRequest = browserSwitchClient.start(this, options); if (pendingRequest instanceof BrowserSwitchPendingRequest.Started) { - String pendingRequestToken = - ((BrowserSwitchPendingRequest.Started) pendingRequest).getToken(); - PendingRequestStore.put(this, pendingRequestToken); + String pendingRequestState = + ((BrowserSwitchPendingRequest.Started) pendingRequest).getPendingRequestState(); + PendingRequestStore.put(this, pendingRequestState); } else if (pendingRequest instanceof BrowserSwitchPendingRequest.Failure) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchPendingRequest.Failure) pendingRequest).getCause()); } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt b/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt index 9c842b8c..53446c7d 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/utils/PendingRequestStore.kt @@ -2,7 +2,6 @@ package com.braintreepayments.api.demo.utils import android.content.Context import android.content.SharedPreferences -import com.braintreepayments.api.BrowserSwitchPendingRequest class PendingRequestStore { @@ -12,12 +11,12 @@ class PendingRequestStore { private const val PENDING_REQUEST_KEY = "BROWSER_SWITCH_REQUEST" @JvmStatic - fun put(context: Context, pendingRequestToken: String) { + fun put(context: Context, pendingRequestState: String) { val sharedPreferences: SharedPreferences = context.getSharedPreferences( SHARED_PREFS_KEY, Context.MODE_PRIVATE ) - sharedPreferences.edit().putString(PENDING_REQUEST_KEY, pendingRequestToken).apply() + sharedPreferences.edit().putString(PENDING_REQUEST_KEY, pendingRequestState).apply() } @JvmStatic From 52ddf2b2344bd07d6441da528676f2106416c34d Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 10:19:27 -0500 Subject: [PATCH 07/13] Refactor naming again. --- .../api/BrowserSwitchClient.java | 1 + .../api/BrowserSwitchPendingRequest.kt | 2 +- .../api/BrowserSwitchResult.kt | 4 +-- .../api/BrowserSwitchClientUnitTest.java | 2 +- .../api/demo/ComposeActivity.kt | 28 +++++++++---------- .../api/demo/DemoActivitySingleTop.java | 2 +- 6 files changed, 19 insertions(+), 20 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 02394887..eb2bbd46 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -114,6 +114,7 @@ private boolean isValidRequestCode(int requestCode) { * {@link BrowserSwitchPendingRequest.Started}. A {@link BrowserSwitchResult.NoResult} will be * returned if the user returns to the app without completing the browser switch flow. */ + @NonNull public BrowserSwitchResult parseResult(@NonNull Intent intent, @NonNull String pendingRequestState) { if (intent != null && intent.getData() != null) { Uri deepLinkUrl = intent.getData(); diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt index 8a603844..9b3fe308 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt @@ -10,7 +10,7 @@ sealed class BrowserSwitchPendingRequest { * A browser switch was successfully started. This pending request should be store dnd passed to * [BrowserSwitchClient.parseResult] */ - class Started(val pendingRequestState: String) : BrowserSwitchPendingRequest() + class Started(val state: String) : BrowserSwitchPendingRequest() /** * An error with [cause] occurred launching the browser diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt index 099d5a59..f77c125d 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt @@ -34,7 +34,7 @@ sealed class BrowserSwitchResult { /** * The browser switch failed. - * @property [reason] Reason for the browser switch failure. + * @property [error] Error detailing the reason for the browser switch failure. */ - class Failure internal constructor(val reason: BrowserSwitchException) : BrowserSwitchResult() + class Failure internal constructor(val error: BrowserSwitchException) : BrowserSwitchResult() } 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 aba1c301..16a5dfb1 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -95,7 +95,7 @@ public void start_whenSuccessful_returnsBrowserSwitchRequest() throws BrowserSwi assertNotNull(browserSwitchPendingRequest); assertTrue(browserSwitchPendingRequest instanceof BrowserSwitchPendingRequest.Started); - String pendingRequestState = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getPendingRequestState(); + String pendingRequestState = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getState(); BrowserSwitchRequest browserSwitchRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestState); assertEquals(browserSwitchRequest.getRequestCode(), 123); assertEquals(browserSwitchRequest.getUrl(), browserSwitchDestinationUrl); diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index 948b46e4..f888805e 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -31,6 +31,7 @@ class ComposeActivity : ComponentActivity() { private val viewModel by viewModels() private lateinit var browserSwitchClient: BrowserSwitchClient + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) browserSwitchClient = BrowserSwitchClient() @@ -47,22 +48,19 @@ class ComposeActivity : ComponentActivity() { override fun onResume() { super.onResume() - val pendingRequestState = PendingRequestStore.get(this) - val browserSwitchResult = pendingRequestState?.let { - browserSwitchClient.parseResult(intent, pendingRequestState) - } - when (browserSwitchResult) { - is BrowserSwitchResult.Success -> { - viewModel.browserSwitchResult = browserSwitchResult - PendingRequestStore.clear(this) - } + PendingRequestStore.get(this)?.let { pendingRequestState -> + when (val result = browserSwitchClient.parseResult(intent, pendingRequestState)) { + is BrowserSwitchResult.Success -> { + viewModel.browserSwitchResult = result + PendingRequestStore.clear(this) + } - is BrowserSwitchResult.NoResult -> - viewModel.browserSwitchError = Exception("User did not complete browser switch") + is BrowserSwitchResult.NoResult -> + viewModel.browserSwitchError = Exception("User did not complete browser switch") - // ignore null case - // TODO: Make parse result @NonNull - else -> {} + is BrowserSwitchResult.Failure -> + viewModel.browserSwitchError = result.error + } } } @@ -76,7 +74,7 @@ class ComposeActivity : ComponentActivity() { .returnUrlScheme(RETURN_URL_SCHEME) when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { is BrowserSwitchPendingRequest.Started -> - PendingRequestStore.put(this, pendingRequest.pendingRequestState) + PendingRequestStore.put(this, pendingRequest.state) is BrowserSwitchPendingRequest.Failure -> viewModel.browserSwitchError = pendingRequest.cause diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java index 0560de88..e4fe7f06 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java @@ -68,7 +68,7 @@ public void startBrowserSwitch(BrowserSwitchOptions options) throws BrowserSwitc BrowserSwitchPendingRequest pendingRequest = browserSwitchClient.start(this, options); if (pendingRequest instanceof BrowserSwitchPendingRequest.Started) { String pendingRequestState = - ((BrowserSwitchPendingRequest.Started) pendingRequest).getPendingRequestState(); + ((BrowserSwitchPendingRequest.Started) pendingRequest).getState(); PendingRequestStore.put(this, pendingRequestState); } else if (pendingRequest instanceof BrowserSwitchPendingRequest.Failure) { Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchPendingRequest.Failure) pendingRequest).getCause()); From dbb0e966f690249ccc977fc653126d273906b9a7 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 10:20:48 -0500 Subject: [PATCH 08/13] Rename local variable for consistency. --- .../java/com/braintreepayments/api/BrowserSwitchClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 eb2bbd46..60c6b674 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -119,10 +119,10 @@ public BrowserSwitchResult parseResult(@NonNull Intent intent, @NonNull String p if (intent != null && intent.getData() != null) { Uri deepLinkUrl = intent.getData(); try { - BrowserSwitchRequest originalRequest = + BrowserSwitchRequest pendingRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestState); - if (originalRequest.matchesDeepLinkUrlScheme(deepLinkUrl)) { - return new BrowserSwitchResult.Success(deepLinkUrl, originalRequest); + if (pendingRequest.matchesDeepLinkUrlScheme(deepLinkUrl)) { + return new BrowserSwitchResult.Success(deepLinkUrl, pendingRequest); } } catch (BrowserSwitchException e) { return new BrowserSwitchResult.Failure(e); From 4d80d7091a88db3f9bbf47f496d69a8f2f9765e1 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 10:32:26 -0500 Subject: [PATCH 09/13] Remove unecessary TODO. --- .../main/java/com/braintreepayments/api/BrowserSwitchClient.java | 1 + .../java/com/braintreepayments/api/BrowserSwitchRequest.java | 1 - 2 files 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 60c6b674..aa1d498d 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -48,6 +48,7 @@ public BrowserSwitchClient() { */ @NonNull public BrowserSwitchPendingRequest start(@NonNull ComponentActivity activity, @NonNull BrowserSwitchOptions browserSwitchOptions) { + // TODO: allow browser switching with application context try { assertCanPerformBrowserSwitch(activity, browserSwitchOptions); } catch (BrowserSwitchException e) { diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java index 90b22f2c..341164ae 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java @@ -51,7 +51,6 @@ static BrowserSwitchRequest fromBase64EncodedJSON(@NonNull String base64EncodedR this.url = url; this.requestCode = requestCode; this.metadata = metadata; - // TODO: make return url scheme accessor public this.returnUrlScheme = returnUrlScheme; } From 3e6fc662a7a3112cfafd6a2ce07f9bd49766aca3 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 10:45:04 -0500 Subject: [PATCH 10/13] Rename BrowserSwitchPendingRequest to BrowserSwitchStartResult. --- .../api/BrowserSwitchClient.java | 30 +++++------ .../api/BrowserSwitchOptions.java | 4 +- ...hResult.kt => BrowserSwitchParseResult.kt} | 19 +++---- ...Request.kt => BrowserSwitchStartResult.kt} | 6 +-- .../api/BrowserSwitchClientUnitTest.java | 52 +++++++++---------- .../api/demo/ComposeActivity.kt | 22 ++++---- .../api/demo/DemoActivitySingleTop.java | 20 +++---- .../api/demo/DemoFragment.java | 4 +- .../demo/viewmodel/BrowserSwitchViewModel.kt | 10 ++-- .../api/demo/viewmodel/UiState.kt | 4 +- 10 files changed, 86 insertions(+), 85 deletions(-) rename browser-switch/src/main/java/com/braintreepayments/api/{BrowserSwitchResult.kt => BrowserSwitchParseResult.kt} (87%) rename browser-switch/src/main/java/com/braintreepayments/api/{BrowserSwitchPendingRequest.kt => BrowserSwitchStartResult.kt} (72%) 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 aa1d498d..9dbaee6e 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -42,17 +42,17 @@ public BrowserSwitchClient() { * * @param activity the activity used to start browser switch * @param browserSwitchOptions {@link BrowserSwitchOptions} the options used to configure the browser switch - * @return a {@link BrowserSwitchPendingRequest.Started} that should be stored and passed to + * @return a {@link BrowserSwitchStartResult.Success} that should be stored and passed to * {@link BrowserSwitchClient#parseResult(Intent, String)} upon return to the app, - * or {@link BrowserSwitchPendingRequest.Failure} if browser could not be launched. + * or {@link BrowserSwitchStartResult.Failure} if browser could not be launched. */ @NonNull - public BrowserSwitchPendingRequest start(@NonNull ComponentActivity activity, @NonNull BrowserSwitchOptions browserSwitchOptions) { + public BrowserSwitchStartResult start(@NonNull ComponentActivity activity, @NonNull BrowserSwitchOptions browserSwitchOptions) { // TODO: allow browser switching with application context try { assertCanPerformBrowserSwitch(activity, browserSwitchOptions); } catch (BrowserSwitchException e) { - return new BrowserSwitchPendingRequest.Failure(e); + return new BrowserSwitchStartResult.Failure(e); } Uri browserSwitchUrl = browserSwitchOptions.getUrl(); @@ -64,16 +64,16 @@ public BrowserSwitchPendingRequest start(@NonNull ComponentActivity activity, @N if (activity.isFinishing()) { String activityFinishingMessage = "Unable to start browser switch while host Activity is finishing."; - return new BrowserSwitchPendingRequest.Failure(new BrowserSwitchException(activityFinishingMessage)); + return new BrowserSwitchStartResult.Failure(new BrowserSwitchException(activityFinishingMessage)); } else { boolean launchAsNewTask = browserSwitchOptions.isLaunchAsNewTask(); try { BrowserSwitchRequest request = new BrowserSwitchRequest(requestCode, browserSwitchUrl, metadata, returnUrlScheme); customTabsInternalClient.launchUrl(activity, browserSwitchUrl, launchAsNewTask); - return new BrowserSwitchPendingRequest.Started(request.toBase64EncodedJSON()); + return new BrowserSwitchStartResult.Success(request.toBase64EncodedJSON()); } catch (ActivityNotFoundException | BrowserSwitchException e) { - return new BrowserSwitchPendingRequest.Failure(new BrowserSwitchException("Unable to start browser switch without a web browser.", e)); + return new BrowserSwitchStartResult.Failure(new BrowserSwitchException("Unable to start browser switch without a web browser.", e)); } } } @@ -108,27 +108,27 @@ private boolean isValidRequestCode(int requestCode) { * * @param intent the intent to return to your application containing a deep link result from the * browser flow - * @param pendingRequestState the {@link BrowserSwitchPendingRequest.Started} token returned from + * @param pendingRequestState the {@link BrowserSwitchStartResult.Success} token returned from * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} - * @return a {@link BrowserSwitchResult.Success} if the browser switch was successfully - * completed, or {@link BrowserSwitchResult.NoResult} if no result can be found for the given - * {@link BrowserSwitchPendingRequest.Started}. A {@link BrowserSwitchResult.NoResult} will be + * @return a {@link BrowserSwitchParseResult.Success} if the browser switch was successfully + * completed, or {@link BrowserSwitchParseResult.None} if no result can be found for the given + * {@link BrowserSwitchStartResult.Success}. A {@link BrowserSwitchParseResult.None} will be * returned if the user returns to the app without completing the browser switch flow. */ @NonNull - public BrowserSwitchResult parseResult(@NonNull Intent intent, @NonNull String pendingRequestState) { + public BrowserSwitchParseResult parseResult(@NonNull Intent intent, @NonNull String pendingRequestState) { if (intent != null && intent.getData() != null) { Uri deepLinkUrl = intent.getData(); try { BrowserSwitchRequest pendingRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestState); if (pendingRequest.matchesDeepLinkUrlScheme(deepLinkUrl)) { - return new BrowserSwitchResult.Success(deepLinkUrl, pendingRequest); + return new BrowserSwitchParseResult.Success(deepLinkUrl, pendingRequest); } } catch (BrowserSwitchException e) { - return new BrowserSwitchResult.Failure(e); + return new BrowserSwitchParseResult.Failure(e); } } - return BrowserSwitchResult.NoResult.INSTANCE; + return BrowserSwitchParseResult.None.INSTANCE; } } diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java index 19d1f53a..ea03fe06 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchOptions.java @@ -9,7 +9,7 @@ /** * Object that contains a set of browser switch parameters for use with - * {@link BrowserSwitchClient#start(FragmentActivity, BrowserSwitchOptions)}. + * {@link BrowserSwitchClient#start(androidx.activity.ComponentActivity, BrowserSwitchOptions)}. */ public class BrowserSwitchOptions { @@ -24,7 +24,7 @@ public class BrowserSwitchOptions { * Set browser switch metadata. * * @param metadata JSONObject containing metadata that will be persisted and returned in a - * {@link BrowserSwitchResult} when the app has re-entered the foreground + * {@link BrowserSwitchParseResult} when the app has re-entered the foreground * @return {@link BrowserSwitchOptions} reference to instance to allow setter invocations to be chained */ public BrowserSwitchOptions metadata(@Nullable JSONObject metadata) { diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt similarity index 87% rename from browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt rename to browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt index f77c125d..980eb5d2 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt @@ -6,7 +6,7 @@ import org.json.JSONObject /** * The result of a browser switch obtained from [BrowserSwitchClient.parseResult] */ -sealed class BrowserSwitchResult { +sealed class BrowserSwitchParseResult { /** * The browser switch was successfully completed. See [resultInfo] for details. @@ -16,7 +16,7 @@ sealed class BrowserSwitchResult { val requestCode: Int, val requestUrl: Uri, val requestMetadata: JSONObject?, - ) : BrowserSwitchResult() { + ) : BrowserSwitchParseResult() { internal constructor(deepLinkUrl: Uri, originalRequest: BrowserSwitchRequest) : this( deepLinkUrl, originalRequest.requestCode, @@ -26,15 +26,16 @@ sealed class BrowserSwitchResult { } /** - * No browser switch result was found. This is the expected result when a user cancels the - * browser switch flow without completing by closing the browser, or navigates back to the app - * without completing the browser switch flow. + * The browser switch failed. + * @property [error] Error detailing the reason for the browser switch failure. */ - object NoResult : BrowserSwitchResult() + class Failure internal constructor(val error: BrowserSwitchException) : + BrowserSwitchParseResult() /** - * The browser switch failed. - * @property [error] Error detailing the reason for the browser switch failure. + * No browser switch result was found. This is the expected result when a user cancels the + * browser switch flow without completing by closing the browser, or navigates back to the app + * without completing the browser switch flow. */ - class Failure internal constructor(val error: BrowserSwitchException) : BrowserSwitchResult() + object None : BrowserSwitchParseResult() } diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt similarity index 72% rename from browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt rename to browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt index 9b3fe308..7c21e745 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchPendingRequest.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt @@ -4,16 +4,16 @@ package com.braintreepayments.api * A pending request for browser switching. This pending request should be stored locally within the app or * on-device and used to deliver a result of the browser flow in [BrowserSwitchClient.parseResult] */ -sealed class BrowserSwitchPendingRequest { +sealed class BrowserSwitchStartResult { /** * A browser switch was successfully started. This pending request should be store dnd passed to * [BrowserSwitchClient.parseResult] */ - class Started(val state: String) : BrowserSwitchPendingRequest() + class Success(val pendingRequestState: String) : BrowserSwitchStartResult() /** * An error with [cause] occurred launching the browser */ - class Failure(val cause: Exception) : BrowserSwitchPendingRequest() + class Failure(val cause: Exception) : BrowserSwitchStartResult() } 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 16a5dfb1..c200c6bc 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -70,9 +70,9 @@ public void start_whenActivityIsFinishing_throwsException() { .returnUrlScheme("return-url-scheme") .metadata(metadata); - BrowserSwitchPendingRequest request = sut.start(componentActivity, options); - assertTrue(request instanceof BrowserSwitchPendingRequest.Failure); - assertEquals(((BrowserSwitchPendingRequest.Failure) request).getCause().getMessage(), "Unable to start browser switch while host Activity is finishing."); + BrowserSwitchStartResult request = sut.start(componentActivity, options); + assertTrue(request instanceof BrowserSwitchStartResult.Failure); + assertEquals(((BrowserSwitchStartResult.Failure) request).getCause().getMessage(), "Unable to start browser switch while host Activity is finishing."); } @Test @@ -88,14 +88,14 @@ public void start_whenSuccessful_returnsBrowserSwitchRequest() throws BrowserSwi .url(browserSwitchDestinationUrl) .returnUrlScheme("return-url-scheme") .metadata(metadata); - BrowserSwitchPendingRequest browserSwitchPendingRequest = sut.start(componentActivity, options); + BrowserSwitchStartResult browserSwitchStartResult = sut.start(componentActivity, options); verify(customTabsInternalClient).launchUrl(componentActivity, browserSwitchDestinationUrl, false); - assertNotNull(browserSwitchPendingRequest); - assertTrue(browserSwitchPendingRequest instanceof BrowserSwitchPendingRequest.Started); + assertNotNull(browserSwitchStartResult); + assertTrue(browserSwitchStartResult instanceof BrowserSwitchStartResult.Success); - String pendingRequestState = ((BrowserSwitchPendingRequest.Started) browserSwitchPendingRequest).getState(); + String pendingRequestState = ((BrowserSwitchStartResult.Success) browserSwitchStartResult).getPendingRequestState(); BrowserSwitchRequest browserSwitchRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequestState); assertEquals(browserSwitchRequest.getRequestCode(), 123); assertEquals(browserSwitchRequest.getUrl(), browserSwitchDestinationUrl); @@ -116,9 +116,9 @@ public void start_whenNoBrowserAvailable_returnsFailure() { .url(browserSwitchDestinationUrl) .returnUrlScheme("return-url-scheme") .metadata(metadata); - BrowserSwitchPendingRequest request = sut.start(componentActivity, options); - assertTrue(request instanceof BrowserSwitchPendingRequest.Failure); - assertEquals(((BrowserSwitchPendingRequest.Failure) request).getCause().getMessage(), "Unable to start browser switch without a web browser."); + BrowserSwitchStartResult request = sut.start(componentActivity, options); + assertTrue(request instanceof BrowserSwitchStartResult.Failure); + assertEquals(((BrowserSwitchStartResult.Failure) request).getCause().getMessage(), "Unable to start browser switch without a web browser."); } @Test @@ -134,9 +134,9 @@ public void start_whenRequestCodeIsIntegerMinValue_returnsFailure() { .url(browserSwitchDestinationUrl) .returnUrlScheme("return-url-scheme") .metadata(metadata); - BrowserSwitchPendingRequest request = sut.start(componentActivity, options); - assertTrue(request instanceof BrowserSwitchPendingRequest.Failure); - assertEquals(((BrowserSwitchPendingRequest.Failure) request).getCause().getMessage(), "Request code cannot be Integer.MIN_VALUE"); + BrowserSwitchStartResult request = sut.start(componentActivity, options); + assertTrue(request instanceof BrowserSwitchStartResult.Failure); + assertEquals(((BrowserSwitchStartResult.Failure) request).getCause().getMessage(), "Request code cannot be Integer.MIN_VALUE"); } @Test @@ -153,12 +153,12 @@ public void start_whenDeviceIsNotConfiguredForDeepLinking_returnsFailure() { .returnUrlScheme("return-url-scheme") .metadata(metadata); - BrowserSwitchPendingRequest request = sut.start(componentActivity, options); - assertTrue(request instanceof BrowserSwitchPendingRequest.Failure); + BrowserSwitchStartResult request = sut.start(componentActivity, options); + assertTrue(request instanceof BrowserSwitchStartResult.Failure); assertEquals("The return url scheme was not set up, incorrectly set up, or more than one " + "Activity on this device defines the same url scheme in it's Android Manifest. " + "See https://github.com/braintree/browser-switch-android for more information on " + - "setting up a return url scheme.", ((BrowserSwitchPendingRequest.Failure) request).getCause().getMessage()); + "setting up a return url scheme.", ((BrowserSwitchStartResult.Failure) request).getCause().getMessage()); } @Test @@ -174,9 +174,9 @@ public void start_whenNoReturnUrlSchemeSet_throwsFailure() { .returnUrlScheme(null) .url(browserSwitchDestinationUrl) .metadata(metadata); - BrowserSwitchPendingRequest request = sut.start(componentActivity, options); - assertTrue(request instanceof BrowserSwitchPendingRequest.Failure); - assertEquals("A returnUrlScheme is required.", ((BrowserSwitchPendingRequest.Failure) request).getCause().getMessage()); + BrowserSwitchStartResult request = sut.start(componentActivity, options); + assertTrue(request instanceof BrowserSwitchStartResult.Failure); + assertEquals("A returnUrlScheme is required.", ((BrowserSwitchStartResult.Failure) request).getCause().getMessage()); } @Test @@ -191,10 +191,10 @@ public void parseResult_whenActiveRequestMatchesDeepLinkResultURLScheme_returnsB Uri deepLinkUrl = Uri.parse("fake-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); - BrowserSwitchResult browserSwitchResult = sut.parseResult(intent, request.toBase64EncodedJSON()); + BrowserSwitchParseResult browserSwitchParseResult = sut.parseResult(intent, request.toBase64EncodedJSON()); - assertTrue(browserSwitchResult instanceof BrowserSwitchResult.Success); - assertEquals(deepLinkUrl, ((BrowserSwitchResult.Success) browserSwitchResult).getDeepLinkUrl()); + assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.Success); + assertEquals(deepLinkUrl, ((BrowserSwitchParseResult.Success) browserSwitchParseResult).getDeepLinkUrl()); } @Test @@ -208,9 +208,9 @@ public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() Uri deepLinkUrl = Uri.parse("a-different-url-scheme://success"); Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); - BrowserSwitchResult browserSwitchResult = sut.parseResult(intent, request.toBase64EncodedJSON()); + BrowserSwitchParseResult browserSwitchParseResult = sut.parseResult(intent, request.toBase64EncodedJSON()); - assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); + assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.None); } @Test @@ -222,7 +222,7 @@ public void parseResult_whenIntentIsNull_returnsNoResult() throws BrowserSwitchE BrowserSwitchRequest request = new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); - BrowserSwitchResult browserSwitchResult = sut.parseResult(null, request.toBase64EncodedJSON()); - assertTrue(browserSwitchResult instanceof BrowserSwitchResult.NoResult); + BrowserSwitchParseResult browserSwitchParseResult = sut.parseResult(null, request.toBase64EncodedJSON()); + assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.None); } } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index f888805e..f5227b16 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -19,8 +19,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.braintreepayments.api.BrowserSwitchClient import com.braintreepayments.api.BrowserSwitchOptions -import com.braintreepayments.api.BrowserSwitchPendingRequest -import com.braintreepayments.api.BrowserSwitchResult +import com.braintreepayments.api.BrowserSwitchStartResult +import com.braintreepayments.api.BrowserSwitchParseResult import com.braintreepayments.api.demo.utils.PendingRequestStore import com.braintreepayments.api.demo.viewmodel.BrowserSwitchViewModel import org.json.JSONObject @@ -50,15 +50,15 @@ class ComposeActivity : ComponentActivity() { super.onResume() PendingRequestStore.get(this)?.let { pendingRequestState -> when (val result = browserSwitchClient.parseResult(intent, pendingRequestState)) { - is BrowserSwitchResult.Success -> { - viewModel.browserSwitchResult = result + is BrowserSwitchParseResult.Success -> { + viewModel.browserSwitchParseResult = result PendingRequestStore.clear(this) } - is BrowserSwitchResult.NoResult -> + is BrowserSwitchParseResult.None -> viewModel.browserSwitchError = Exception("User did not complete browser switch") - is BrowserSwitchResult.Failure -> + is BrowserSwitchParseResult.Failure -> viewModel.browserSwitchError = result.error } } @@ -73,10 +73,10 @@ class ComposeActivity : ComponentActivity() { .launchAsNewTask(false) .returnUrlScheme(RETURN_URL_SCHEME) when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { - is BrowserSwitchPendingRequest.Started -> - PendingRequestStore.put(this, pendingRequest.state) + is BrowserSwitchStartResult.Success -> + PendingRequestStore.put(this, pendingRequest.pendingRequestState) - is BrowserSwitchPendingRequest.Failure -> viewModel.browserSwitchError = + is BrowserSwitchStartResult.Failure -> viewModel.browserSwitchError = pendingRequest.cause } } @@ -99,7 +99,7 @@ class ComposeActivity : ComponentActivity() { @Composable fun BrowserSwitchResult(viewModel: BrowserSwitchViewModel) { val uiState = viewModel.uiState.collectAsState().value - uiState.browserSwitchResult?.let { + uiState.browserSwitchParseResult?.let { BrowserSwitchSuccess(result = it) } uiState.browserSwitchError?.let { BrowserSwitchError(exception = it) } @@ -117,7 +117,7 @@ fun BrowserSwitchButton(onClick: () -> Unit) { } @Composable -fun BrowserSwitchSuccess(result: BrowserSwitchResult.Success) { +fun BrowserSwitchSuccess(result: BrowserSwitchParseResult.Success) { val returnUrl = result.deepLinkUrl val color = returnUrl.getQueryParameter("color") diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java index e4fe7f06..7db9e79d 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java @@ -12,8 +12,8 @@ import com.braintreepayments.api.BrowserSwitchClient; import com.braintreepayments.api.BrowserSwitchException; import com.braintreepayments.api.BrowserSwitchOptions; -import com.braintreepayments.api.BrowserSwitchPendingRequest; -import com.braintreepayments.api.BrowserSwitchResult; +import com.braintreepayments.api.BrowserSwitchStartResult; +import com.braintreepayments.api.BrowserSwitchParseResult; import com.braintreepayments.api.demo.utils.PendingRequestStore; import java.util.Objects; @@ -45,9 +45,9 @@ protected void onNewIntent(Intent intent) { String pendingRequestState = PendingRequestStore.get(this); if (pendingRequestState != null) { - BrowserSwitchResult result = browserSwitchClient.parseResult(intent, pendingRequestState); - if (result instanceof BrowserSwitchResult.Success) { - Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchResult.Success) result)); + BrowserSwitchParseResult result = browserSwitchClient.parseResult(intent, pendingRequestState); + if (result instanceof BrowserSwitchParseResult.Success) { + Objects.requireNonNull(getDemoFragment()).onBrowserSwitchResult(((BrowserSwitchParseResult.Success) result)); } PendingRequestStore.clear(this); } @@ -65,13 +65,13 @@ protected void onResume() { } public void startBrowserSwitch(BrowserSwitchOptions options) throws BrowserSwitchException { - BrowserSwitchPendingRequest pendingRequest = browserSwitchClient.start(this, options); - if (pendingRequest instanceof BrowserSwitchPendingRequest.Started) { + BrowserSwitchStartResult pendingRequest = browserSwitchClient.start(this, options); + if (pendingRequest instanceof BrowserSwitchStartResult.Success) { String pendingRequestState = - ((BrowserSwitchPendingRequest.Started) pendingRequest).getState(); + ((BrowserSwitchStartResult.Success) pendingRequest).getPendingRequestState(); PendingRequestStore.put(this, pendingRequestState); - } else if (pendingRequest instanceof BrowserSwitchPendingRequest.Failure) { - Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchPendingRequest.Failure) pendingRequest).getCause()); + } else if (pendingRequest instanceof BrowserSwitchStartResult.Failure) { + Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchStartResult.Failure) pendingRequest).getCause()); } } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java index d682bfe5..e940c317 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoFragment.java @@ -15,7 +15,7 @@ import com.braintreepayments.api.BrowserSwitchException; import com.braintreepayments.api.BrowserSwitchOptions; -import com.braintreepayments.api.BrowserSwitchResult; +import com.braintreepayments.api.BrowserSwitchParseResult; import org.json.JSONException; import org.json.JSONObject; @@ -99,7 +99,7 @@ private void clearTextViews() { mMetadataTextView.setText(""); } - public void onBrowserSwitchResult(BrowserSwitchResult.Success result) { + public void onBrowserSwitchResult(BrowserSwitchParseResult.Success result) { String selectedColorText = ""; String resultText = "Browser Switch Successful"; diff --git a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt index 97c3d5fe..e13653f8 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/BrowserSwitchViewModel.kt @@ -1,7 +1,7 @@ package com.braintreepayments.api.demo.viewmodel import androidx.lifecycle.ViewModel -import com.braintreepayments.api.BrowserSwitchResult +import com.braintreepayments.api.BrowserSwitchParseResult import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -12,10 +12,10 @@ class BrowserSwitchViewModel : ViewModel() { private val _uiState = MutableStateFlow(UiState()) val uiState: StateFlow = _uiState.asStateFlow() - var browserSwitchResult : BrowserSwitchResult.Success? - get() = _uiState.value.browserSwitchResult + var browserSwitchParseResult : BrowserSwitchParseResult.Success? + get() = _uiState.value.browserSwitchParseResult set(value) { - _uiState.update { it.copy(browserSwitchResult = value) } + _uiState.update { it.copy(browserSwitchParseResult = value) } _uiState.update { it.copy(browserSwitchError = null) } } @@ -23,6 +23,6 @@ class BrowserSwitchViewModel : ViewModel() { get() = _uiState.value.browserSwitchError set(value) { _uiState.update { it.copy(browserSwitchError = value) } - _uiState.update { it.copy(browserSwitchResult = null) } + _uiState.update { it.copy(browserSwitchParseResult = null) } } } \ No newline at end of file diff --git a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt index 6e90bfc9..10bb9789 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/viewmodel/UiState.kt @@ -1,8 +1,8 @@ package com.braintreepayments.api.demo.viewmodel -import com.braintreepayments.api.BrowserSwitchResult +import com.braintreepayments.api.BrowserSwitchParseResult data class UiState( - val browserSwitchResult: BrowserSwitchResult.Success? = null, + val browserSwitchParseResult: BrowserSwitchParseResult.Success? = null, val browserSwitchError: Exception? = null, ) \ No newline at end of file From 06ead6c424a35bafb35263122a82f38811b3ea04 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 10:48:51 -0500 Subject: [PATCH 11/13] Revert no result naming. --- .../java/com/braintreepayments/api/BrowserSwitchClient.java | 6 +++--- .../com/braintreepayments/api/BrowserSwitchParseResult.kt | 2 +- .../braintreepayments/api/BrowserSwitchClientUnitTest.java | 4 ++-- .../java/com/braintreepayments/api/demo/ComposeActivity.kt | 6 +++--- 4 files changed, 9 insertions(+), 9 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 9dbaee6e..7f7a2604 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchClient.java @@ -111,8 +111,8 @@ private boolean isValidRequestCode(int requestCode) { * @param pendingRequestState the {@link BrowserSwitchStartResult.Success} token returned from * {@link BrowserSwitchClient#start(ComponentActivity, BrowserSwitchOptions)} * @return a {@link BrowserSwitchParseResult.Success} if the browser switch was successfully - * completed, or {@link BrowserSwitchParseResult.None} if no result can be found for the given - * {@link BrowserSwitchStartResult.Success}. A {@link BrowserSwitchParseResult.None} will be + * completed, or {@link BrowserSwitchParseResult.NoResult} if no result can be found for the given + * {@link BrowserSwitchStartResult.Success}. A {@link BrowserSwitchParseResult.NoResult} will be * returned if the user returns to the app without completing the browser switch flow. */ @NonNull @@ -129,6 +129,6 @@ public BrowserSwitchParseResult parseResult(@NonNull Intent intent, @NonNull Str return new BrowserSwitchParseResult.Failure(e); } } - return BrowserSwitchParseResult.None.INSTANCE; + return BrowserSwitchParseResult.NoResult.INSTANCE; } } diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt index 980eb5d2..5c7a81dc 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchParseResult.kt @@ -37,5 +37,5 @@ sealed class BrowserSwitchParseResult { * browser switch flow without completing by closing the browser, or navigates back to the app * without completing the browser switch flow. */ - object None : BrowserSwitchParseResult() + object NoResult : BrowserSwitchParseResult() } 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 c200c6bc..c3535613 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -210,7 +210,7 @@ public void parseResult_whenDeepLinkResultURLSchemeDoesntMatch_returnsNoResult() Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl); BrowserSwitchParseResult browserSwitchParseResult = sut.parseResult(intent, request.toBase64EncodedJSON()); - assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.None); + assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.NoResult); } @Test @@ -223,6 +223,6 @@ public void parseResult_whenIntentIsNull_returnsNoResult() throws BrowserSwitchE new BrowserSwitchRequest(123, browserSwitchDestinationUrl, requestMetadata, "fake-url-scheme"); BrowserSwitchParseResult browserSwitchParseResult = sut.parseResult(null, request.toBase64EncodedJSON()); - assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.None); + assertTrue(browserSwitchParseResult instanceof BrowserSwitchParseResult.NoResult); } } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index f5227b16..fde789f6 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -55,11 +55,11 @@ class ComposeActivity : ComponentActivity() { PendingRequestStore.clear(this) } - is BrowserSwitchParseResult.None -> - viewModel.browserSwitchError = Exception("User did not complete browser switch") - is BrowserSwitchParseResult.Failure -> viewModel.browserSwitchError = result.error + + is BrowserSwitchParseResult.NoResult -> + viewModel.browserSwitchError = Exception("User did not complete browser switch") } } } From 8466f63f10a01e01ea92fec2eada6ecbb8469bec Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 13:13:10 -0500 Subject: [PATCH 12/13] Add TODO and update CHANGELOG. --- CHANGELOG.md | 4 ++-- .../java/com/braintreepayments/api/BrowserSwitchRequest.java | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 45ec2392..29ca458c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,9 +10,9 @@ * Change `BrowserSwitchClient#start` parameters and return type * Change `BrowserSwitchClient#parseResult` parameters * Remove `deliverResult`, `getResult`, `captureResult`, `clearActiveRequests`, `getResultFromCache`, and `deliverResultFromCache` from `BrowserSwitchClient` - * Add `BrowserSwitchRequest` and `BrowserSwitchPendingRequest` - * Convert `BrowserSwitchResult` to sealed class and add `BrowserSwitchResultInfo` * Remove `BrowserSwitchStatus` + * Add `BrowserSwitchRequest` and `BrowserSwitchStartResult` + * Rename `BrowserSwitchResult` to `BrowserSwitchParseResult` and convert it to a sealed class ## 2.6.1 diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java index 341164ae..dac5e9a9 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchRequest.java @@ -17,6 +17,7 @@ // TODO: consider encryption // Ref: https://medium.com/fw-engineering/sharedpreferences-and-android-keystore-c4eac3373ac7 +// TODO: Rename to `BrowserSwitchStartRequest` and remove `BrowserSwitchOptions` in favor this class public class BrowserSwitchRequest { private static final String KEY_REQUEST_CODE = "requestCode"; From 3b8bf6256dde8580966094763d1ac8503714ee71 Mon Sep 17 00:00:00 2001 From: sshropshire Date: Tue, 2 Apr 2024 13:31:48 -0500 Subject: [PATCH 13/13] Update v3 MIGRATION GUIDE. --- .../api/BrowserSwitchStartResult.kt | 8 ++-- .../api/BrowserSwitchClientUnitTest.java | 10 ++--- .../api/demo/ComposeActivity.kt | 11 ++--- .../api/demo/DemoActivitySingleTop.java | 2 +- v3_MIGRATION_GUIDE.md | 42 +++++++++++++++---- 5 files changed, 47 insertions(+), 26 deletions(-) diff --git a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt index 7c21e745..26aaf48f 100644 --- a/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt +++ b/browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchStartResult.kt @@ -7,13 +7,13 @@ package com.braintreepayments.api sealed class BrowserSwitchStartResult { /** - * A browser switch was successfully started. This pending request should be store dnd passed to - * [BrowserSwitchClient.parseResult] + * Browser switch successfully started. Keep a reference to pending request state and pass it to + * [BrowserSwitchClient.parseResult] after a deep link back into the app has occurred. */ class Success(val pendingRequestState: String) : BrowserSwitchStartResult() /** - * An error with [cause] occurred launching the browser + * Browser switch failed with an [error]. */ - class Failure(val cause: Exception) : BrowserSwitchStartResult() + class Failure(val error: Exception) : BrowserSwitchStartResult() } 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 c3535613..d041ff44 100644 --- a/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java +++ b/browser-switch/src/test/java/com/braintreepayments/api/BrowserSwitchClientUnitTest.java @@ -72,7 +72,7 @@ public void start_whenActivityIsFinishing_throwsException() { BrowserSwitchStartResult request = sut.start(componentActivity, options); assertTrue(request instanceof BrowserSwitchStartResult.Failure); - assertEquals(((BrowserSwitchStartResult.Failure) request).getCause().getMessage(), "Unable to start browser switch while host Activity is finishing."); + assertEquals(((BrowserSwitchStartResult.Failure) request).getError().getMessage(), "Unable to start browser switch while host Activity is finishing."); } @Test @@ -118,7 +118,7 @@ public void start_whenNoBrowserAvailable_returnsFailure() { .metadata(metadata); BrowserSwitchStartResult request = sut.start(componentActivity, options); assertTrue(request instanceof BrowserSwitchStartResult.Failure); - assertEquals(((BrowserSwitchStartResult.Failure) request).getCause().getMessage(), "Unable to start browser switch without a web browser."); + assertEquals(((BrowserSwitchStartResult.Failure) request).getError().getMessage(), "Unable to start browser switch without a web browser."); } @Test @@ -136,7 +136,7 @@ public void start_whenRequestCodeIsIntegerMinValue_returnsFailure() { .metadata(metadata); BrowserSwitchStartResult request = sut.start(componentActivity, options); assertTrue(request instanceof BrowserSwitchStartResult.Failure); - assertEquals(((BrowserSwitchStartResult.Failure) request).getCause().getMessage(), "Request code cannot be Integer.MIN_VALUE"); + assertEquals(((BrowserSwitchStartResult.Failure) request).getError().getMessage(), "Request code cannot be Integer.MIN_VALUE"); } @Test @@ -158,7 +158,7 @@ public void start_whenDeviceIsNotConfiguredForDeepLinking_returnsFailure() { assertEquals("The return url scheme was not set up, incorrectly set up, or more than one " + "Activity on this device defines the same url scheme in it's Android Manifest. " + "See https://github.com/braintree/browser-switch-android for more information on " + - "setting up a return url scheme.", ((BrowserSwitchStartResult.Failure) request).getCause().getMessage()); + "setting up a return url scheme.", ((BrowserSwitchStartResult.Failure) request).getError().getMessage()); } @Test @@ -176,7 +176,7 @@ public void start_whenNoReturnUrlSchemeSet_throwsFailure() { .metadata(metadata); BrowserSwitchStartResult request = sut.start(componentActivity, options); assertTrue(request instanceof BrowserSwitchStartResult.Failure); - assertEquals("A returnUrlScheme is required.", ((BrowserSwitchStartResult.Failure) request).getCause().getMessage()); + assertEquals("A returnUrlScheme is required.", ((BrowserSwitchStartResult.Failure) request).getError().getMessage()); } @Test diff --git a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt index fde789f6..a13dd5d3 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt +++ b/demo/src/main/java/com/braintreepayments/api/demo/ComposeActivity.kt @@ -29,13 +29,10 @@ import java.lang.Exception class ComposeActivity : ComponentActivity() { private val viewModel by viewModels() - - private lateinit var browserSwitchClient: BrowserSwitchClient + private val browserSwitchClient = BrowserSwitchClient() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - browserSwitchClient = BrowserSwitchClient() - setContent { Column(modifier = Modifier.padding(10.dp)) { BrowserSwitchButton { @@ -72,12 +69,12 @@ class ComposeActivity : ComponentActivity() { .url(url) .launchAsNewTask(false) .returnUrlScheme(RETURN_URL_SCHEME) - when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { + when (val result = browserSwitchClient.start(this, browserSwitchOptions)) { is BrowserSwitchStartResult.Success -> - PendingRequestStore.put(this, pendingRequest.pendingRequestState) + PendingRequestStore.put(this, result.pendingRequestState) is BrowserSwitchStartResult.Failure -> viewModel.browserSwitchError = - pendingRequest.cause + result.error } } diff --git a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java index 7db9e79d..77c980fe 100644 --- a/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java +++ b/demo/src/main/java/com/braintreepayments/api/demo/DemoActivitySingleTop.java @@ -71,7 +71,7 @@ public void startBrowserSwitch(BrowserSwitchOptions options) throws BrowserSwitc ((BrowserSwitchStartResult.Success) pendingRequest).getPendingRequestState(); PendingRequestStore.put(this, pendingRequestState); } else if (pendingRequest instanceof BrowserSwitchStartResult.Failure) { - Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchStartResult.Failure) pendingRequest).getCause()); + Objects.requireNonNull(getDemoFragment()).onBrowserSwitchError(((BrowserSwitchStartResult.Failure) pendingRequest).getError()); } } diff --git a/v3_MIGRATION_GUIDE.md b/v3_MIGRATION_GUIDE.md index 686907bb..e4474522 100644 --- a/v3_MIGRATION_GUIDE.md +++ b/v3_MIGRATION_GUIDE.md @@ -43,8 +43,10 @@ Then, add an `intent-filter` in the `AndroidManifest.xml` to your deep link dest class MyActivity : ComponentActivity() { val browserSwitchClient: BrowserSwitchClient = BrowserSwitchClient() + val pendingRequestState: String? = null override fun onResume() { + super.onResume() handleReturnToAppFromBrowser(intent) } @@ -54,25 +56,33 @@ class MyActivity : ComponentActivity() { url = "https://example.com" returnUrlScheme = "my-custom-url-scheme" } - when (val pendingRequest = browserSwitchClient.start(this, browserSwitchOptions)) { + when (val result = browserSwitchClient.start(this, browserSwitchOptions)) { is BrowserSwitchPendingRequest.Started -> { // store pending request + pendingRequestState = result.pendingRequestState } is BrowserSwitchPendingRequest.Failure -> { // browser was unable to be launched, handle failure + Log.d("MyActivity", result.error) } } } fun handleReturnToAppFromBrowser(intent: Intent) { - // fetch stored pending request - fetchPendingRequestFromPersistentStorage()?.let { startedRequest -> - when (val browserSwitchResult = browserSwitchClient.parseResult(startedRequest, intent)) { - is BrowserSwitchResult.Success -> { + if (pendingRequestState != null) { + when (val result = browserSwitchClient.parseResult(intent, pendingRequestState)) { + is BrowserSwitchParseResult.Success -> { // handle successful browser switch result - // clear stored pending request + // drop reference to pending request + pendingRequestState = null } - is BrowserSwitchResult.NoResult -> { + is BrowserSwitchParseResult.Failure -> { + // browser switch parsing failed + Log.d("MyActivity", result.error) + // drop reference to pending request + pendingRequestState = null + } + is BrowserSwitchParseResult.NoResult -> { // user did not complete browser switch // allow user to complete browser switch, or clear stored pending request } @@ -89,14 +99,28 @@ If your deep link destination activity is configured in the `AndroidManifest.xml ```kotlin class MySingleTopActivity : ComponentActivity() { + val pendingRequestState: String? = null val browserSwitchClient: BrowserSwitchClient = BrowserSwitchClient() + + override fun onCreate() { + super.onCreate() + /** + * TODO: initialize pendingRequestState from your app's preferred persistence store + * e.g. shared prefs, data store or saved instance state + */ + pendingRequestState = ... + } override fun onResume() { - // do nothing + super.onResume() + + // handle browser switch when deep link triggers a cold start of the app + handleReturnToAppFromBrowser(intent, pendingRequestState) } override fun onNewIntent(newIntent: Intent) { - handleReturnToAppFromBrowser(newIntent) + // handle browser switch when deep link brings already running singleTop activity to the foreground + handleReturnToAppFromBrowser(newIntent, pendingRequestState) } } ``` \ No newline at end of file