Skip to content
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
* Upgrade `compileSdkVersion` and `targetSdkVersion` to API 36
* Replace `ChromeCustomTabsInternalClient.java` with `AuthTabInternalClient.kt`
* Add parameterized constructor `BrowserSwitchClient(ActivityResultCaller)` to initialize AuthTab support
* Add constructor `BrowserSwitchClient(ActivityResultCaller, String)` for process kill recovery
* Maintain default constructor `BrowserSwitchClient()` (without AuthTab support) for backward compatibility

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny tiny nit: not sure if the (without AuthTab support) is necessary and it make the entry a bit long

* Add `restorePendingRequest()` method to handle process kill recovery


## 3.2.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,27 +73,6 @@ public BrowserSwitchClient(@NonNull ActivityResultCaller caller) {
initializeAuthTabLauncher(caller);
}


/**
* Constructor to initialize BrowserSwitchClient with a pending request to handle process kill scenarios.
* <p>
* When an app is killed during a browser switch, the pending request is lost. To properly handle this:
* <ol>
* <li>Store the pendingRequest string from {@link BrowserSwitchStartResult.Started} in persistent storage</li>
* <li>In {@code onCreate()}, check if there's a stored pending request</li>
* <li>If present, initialize {@code BrowserSwitchClient} with this constructor</li>
* </ol>
* </p>
*
* @param caller The ActivityResultCaller used to initialize the Auth Tab launcher
* @param pendingRequest The base64 encoded JSON string of the pending browser switch request retrieved from persistent storage
* @throws BrowserSwitchException if the pendingRequest cannot be parsed
*/
public BrowserSwitchClient(@NonNull ActivityResultCaller caller, @NonNull String pendingRequest) throws BrowserSwitchException {
this(new BrowserSwitchInspector(), new AuthTabInternalClient());
initializeAuthTabLauncher(caller);
this.pendingAuthTabRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequest);
}
@VisibleForTesting
BrowserSwitchClient(BrowserSwitchInspector browserSwitchInspector,
AuthTabInternalClient authTabInternalClient) {
Expand Down Expand Up @@ -141,6 +120,25 @@ private void initializeAuthTabLauncher(@NonNull ActivityResultCaller caller) {
);
}


/**
* Restores a pending request after process kill or app restart.
*
* <p>Use this method to restore the browser switch state when the app process is killed while the
* browser is open. This should be called in the Activity's {@code onCreate()} method and before calling
* {@link #completeRequest(Intent, String)} to ensure the pending request is properly restored.
*
* <p>The {@code pendingRequest} parameter is the string returned by
* {@link BrowserSwitchStartResult.Started#getPendingRequest()} that was stored in persistent storage
* before the process was killed.
*
* @param pendingRequest The Base64-encoded JSON string representing the pending request to restore
* @throws BrowserSwitchException if the pending request cannot be parsed
*/
public void restorePendingRequest(@NonNull String pendingRequest) throws BrowserSwitchException {
this.pendingAuthTabRequest = BrowserSwitchRequest.fromBase64EncodedJSON(pendingRequest);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did we want to validate that the pendingRequest sent is non-null?
@nonnull annotation on java is just for compiler, it doesn't give any guarantees, AFAIK.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hey, that's a great suggestion, I added it to befcdd1

}

/**
* Open a browser or Auth Tab with a given set of {@link BrowserSwitchOptions} from an Android activity.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,34 @@ public void defaultConstructor_doesNotInitializeAuthTabLauncher() {
}
}

@Test
public void restorePendingRequest_setsInternalPendingAuthTabRequest() throws BrowserSwitchException {
BrowserSwitchClient sut = new BrowserSwitchClient(browserSwitchInspector,
authTabInternalClient);

JSONObject metadata = new JSONObject();
BrowserSwitchRequest originalRequest = new BrowserSwitchRequest(
123,
browserSwitchDestinationUrl,
metadata,
"return-url-scheme",
null
);
String pendingRequestString = originalRequest.toBase64EncodedJSON();

sut.restorePendingRequest(pendingRequestString);

Uri deepLinkUrl = Uri.parse("return-url-scheme://success");
Intent intent = new Intent(Intent.ACTION_VIEW, deepLinkUrl);
BrowserSwitchFinalResult result = sut.completeRequest(intent, pendingRequestString);

assertTrue(result instanceof BrowserSwitchFinalResult.Success);
BrowserSwitchFinalResult.Success successResult = (BrowserSwitchFinalResult.Success) result;
assertEquals(deepLinkUrl, successResult.getReturnUrl());
assertEquals(123, successResult.getRequestCode());
assertEquals(browserSwitchDestinationUrl, successResult.getRequestUrl());
}

@Test
public void completeRequest_whenAppLinkMatches_successReturnedWithAppLink() throws BrowserSwitchException, JSONException {
Uri appLinkUri = Uri.parse("https://example.com");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.braintreepayments.api.BrowserSwitchClient
import com.braintreepayments.api.BrowserSwitchException
import com.braintreepayments.api.BrowserSwitchFinalResult
import com.braintreepayments.api.BrowserSwitchOptions
import com.braintreepayments.api.BrowserSwitchStartResult
Expand All @@ -33,14 +34,15 @@ class ComposeActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
browserSwitchClient = BrowserSwitchClient(this)
// Check if there is a preserved pending request after the process kill
val pendingRequest = PendingRequestStore.get(this)
browserSwitchClient = if (pendingRequest != null) {
// Restore state after process kill
BrowserSwitchClient(this, pendingRequest)
} else {
// Normal initialization
BrowserSwitchClient(this)
PendingRequestStore.get(this)?.let { pendingRequest ->
try {
// Restore pending request after process kill
browserSwitchClient.restorePendingRequest(pendingRequest)
} catch (e: BrowserSwitchException) {
PendingRequestStore.clear(this)
}
}
setContent {
Column(modifier = Modifier.safeGesturesPadding()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,17 @@ public class DemoActivitySingleTop extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

browserSwitchClient = new BrowserSwitchClient(this);
// Check if there is a preserved pending request after the process kill

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The code here seems pretty self explanatory so I'm not sure if the comments here add much

String pendingRequest = PendingRequestStore.get(this);

if (pendingRequest != null) {
// Restore state after process kill
// Restore pending request after process kill
try {
browserSwitchClient = new BrowserSwitchClient(this, pendingRequest);
browserSwitchClient.restorePendingRequest(pendingRequest);
} catch (BrowserSwitchException e) {
PendingRequestStore.clear(this);
browserSwitchClient = new BrowserSwitchClient(this);
}
} else {
// Normal initialization
browserSwitchClient = new BrowserSwitchClient(this);
}

FragmentManager fm = getSupportFragmentManager();
Expand Down