-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
250 additions
and
99 deletions.
There are no files selected for viewing
86 changes: 39 additions & 47 deletions
86
browser-switch/src/main/java/com/braintreepayments/api/BrowserSwitchLauncher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,70 +1,62 @@ | ||
package com.braintreepayments.api; | ||
|
||
import android.content.ActivityNotFoundException; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.net.Uri; | ||
|
||
import androidx.activity.ComponentActivity; | ||
import androidx.activity.result.ActivityResultLauncher; | ||
import androidx.activity.result.ActivityResultRegistry; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.annotation.VisibleForTesting; | ||
import androidx.fragment.app.Fragment; | ||
import androidx.fragment.app.FragmentActivity; | ||
import androidx.lifecycle.LifecycleOwner; | ||
|
||
import java.util.UUID; | ||
|
||
class BrowserSwitchLauncher { | ||
public class BrowserSwitchLauncher { | ||
|
||
private static final String BROWSER_SWITCH_RESULT = "com.braintreepayments.api.BrowserSwitch.RESULT"; | ||
private BrowserSwitchLauncherCallback callback; | ||
private InternalBrowserSwitchLauncher internalBrowserSwitchLauncher; | ||
private BrowserSwitchResult browserSwitchResult; | ||
|
||
ActivityResultLauncher<BrowserSwitchOptions> activityLauncher; | ||
private Intent handledIntent; | ||
|
||
BrowserSwitchLauncher(@NonNull ComponentActivity activity, | ||
@NonNull BrowserSwitchLauncherCallback callback) { | ||
this(activity.getActivityResultRegistry(), activity, callback); | ||
public BrowserSwitchLauncher(Fragment fragment, BrowserSwitchLauncherCallback callback) { | ||
this.callback = callback; | ||
this.internalBrowserSwitchLauncher = new InternalBrowserSwitchLauncher(fragment, browserSwitchResult -> { | ||
this.browserSwitchResult = browserSwitchResult; | ||
}); | ||
} | ||
|
||
BrowserSwitchLauncher(@NonNull Fragment fragment, | ||
@NonNull BrowserSwitchLauncherCallback callback) { | ||
this(fragment.getActivity().getActivityResultRegistry(), fragment.getViewLifecycleOwner(), | ||
callback); | ||
public BrowserSwitchLauncher(ComponentActivity activity, BrowserSwitchLauncherCallback callback) { | ||
this.callback = callback; | ||
this.internalBrowserSwitchLauncher = new InternalBrowserSwitchLauncher(activity, browserSwitchResult -> { | ||
// In the case of a true user cancellation (user closes the browser without completing), | ||
// this result will be delivered before handleReturnToAppFromBrowser is invoked in | ||
// onResume. After a successful result is delivered, the browser activity will be closed | ||
// in the background, delivering a false cancel result. | ||
this.browserSwitchResult = browserSwitchResult; | ||
}); | ||
} | ||
|
||
BrowserSwitchLauncher(ActivityResultRegistry registry, LifecycleOwner lifecycleOwner, | ||
BrowserSwitchLauncherCallback callback) { | ||
activityLauncher = registry.register(BROWSER_SWITCH_RESULT, lifecycleOwner, | ||
new BrowserSwitchActivityResultContract(), callback::onResult); | ||
public void launch(BrowserSwitchOptions browserSwitchOptions) throws BrowserSwitchException { | ||
internalBrowserSwitchLauncher.launch(browserSwitchOptions); | ||
} | ||
|
||
void launch(BrowserSwitchOptions browserSwitchOptions) throws BrowserSwitchException { | ||
try { | ||
activityLauncher.launch(browserSwitchOptions); | ||
} catch (ActivityNotFoundException e) { | ||
throw new BrowserSwitchException(e.getMessage()); | ||
} | ||
} | ||
|
||
void clearActiveRequests(@NonNull Context context) { | ||
BrowserSwitchPersistentStore.getInstance().clearActiveRequest(context.getApplicationContext()); | ||
} | ||
|
||
@Nullable | ||
public BrowserSwitchResult parseResult(@NonNull Context context, int requestCode, @Nullable Intent intent) { | ||
public void handleReturnToAppFromBrowser(@NonNull Context context, int requestId, @NonNull Intent intent) { | ||
BrowserSwitchResult result = null; | ||
if (intent != null && intent.getData() != null) { | ||
BrowserSwitchRequest request = | ||
BrowserSwitchPersistentStore.getInstance().getActiveRequest(context.getApplicationContext()); | ||
if (request != null && request.getRequestCode() == requestCode) { | ||
Uri deepLinkUrl = intent.getData(); | ||
if (request.matchesDeepLinkUrlScheme(deepLinkUrl)) { | ||
result = new BrowserSwitchResult(BrowserSwitchStatus.SUCCESS, request, deepLinkUrl); | ||
} | ||
} | ||
// If browser-switch is re-launched from the same activity, a false previous success result | ||
// may be re-delivered even if the user cancels this time, check if we have already handled | ||
// the intent before delivering a success result again | ||
if (!intent.equals(handledIntent)) { | ||
// Handle successful deep link back to app first, to avoid false cancel result (above) | ||
result = internalBrowserSwitchLauncher.parseResult(context, requestId, intent); | ||
} | ||
if (result == null) { | ||
// If the app was not successfully resumed via deep link, check if there was a true | ||
// cancel result | ||
result = this.browserSwitchResult; | ||
} | ||
if (result != null) { | ||
callback.onResult(result); | ||
internalBrowserSwitchLauncher.clearActiveRequests(context); | ||
this.browserSwitchResult = null; | ||
handledIntent = intent; | ||
} | ||
return result; | ||
} | ||
} |
66 changes: 66 additions & 0 deletions
66
browser-switch/src/main/java/com/braintreepayments/api/InternalBrowserSwitchLauncher.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package com.braintreepayments.api; | ||
|
||
import android.content.ActivityNotFoundException; | ||
import android.content.Context; | ||
import android.content.Intent; | ||
import android.net.Uri; | ||
|
||
import androidx.activity.ComponentActivity; | ||
import androidx.activity.result.ActivityResultLauncher; | ||
import androidx.activity.result.ActivityResultRegistry; | ||
import androidx.annotation.NonNull; | ||
import androidx.annotation.Nullable; | ||
import androidx.fragment.app.Fragment; | ||
import androidx.lifecycle.LifecycleOwner; | ||
|
||
class InternalBrowserSwitchLauncher { | ||
|
||
private static final String BROWSER_SWITCH_RESULT = "com.braintreepayments.api.BrowserSwitch.RESULT"; | ||
|
||
ActivityResultLauncher<BrowserSwitchOptions> activityLauncher; | ||
|
||
InternalBrowserSwitchLauncher(@NonNull ComponentActivity activity, | ||
@NonNull BrowserSwitchLauncherCallback callback) { | ||
this(activity.getActivityResultRegistry(), activity, callback); | ||
} | ||
|
||
InternalBrowserSwitchLauncher(@NonNull Fragment fragment, | ||
@NonNull BrowserSwitchLauncherCallback callback) { | ||
this(fragment.getActivity().getActivityResultRegistry(), fragment.getViewLifecycleOwner(), | ||
callback); | ||
} | ||
|
||
InternalBrowserSwitchLauncher(ActivityResultRegistry registry, LifecycleOwner lifecycleOwner, | ||
BrowserSwitchLauncherCallback callback) { | ||
activityLauncher = registry.register(BROWSER_SWITCH_RESULT, lifecycleOwner, | ||
new BrowserSwitchActivityResultContract(), callback::onResult); | ||
} | ||
|
||
void launch(BrowserSwitchOptions browserSwitchOptions) throws BrowserSwitchException { | ||
try { | ||
activityLauncher.launch(browserSwitchOptions); | ||
} catch (ActivityNotFoundException e) { | ||
throw new BrowserSwitchException(e.getMessage()); | ||
} | ||
} | ||
|
||
void clearActiveRequests(@NonNull Context context) { | ||
BrowserSwitchPersistentStore.getInstance().clearActiveRequest(context.getApplicationContext()); | ||
} | ||
|
||
@Nullable | ||
public BrowserSwitchResult parseResult(@NonNull Context context, int requestCode, @Nullable Intent intent) { | ||
BrowserSwitchResult result = null; | ||
if (intent != null && intent.getData() != null) { | ||
BrowserSwitchRequest request = | ||
BrowserSwitchPersistentStore.getInstance().getActiveRequest(context.getApplicationContext()); | ||
if (request != null && request.getRequestCode() == requestCode) { | ||
Uri deepLinkUrl = intent.getData(); | ||
if (request.matchesDeepLinkUrlScheme(deepLinkUrl)) { | ||
result = new BrowserSwitchResult(BrowserSwitchStatus.SUCCESS, request, deepLinkUrl); | ||
} | ||
} | ||
} | ||
return result; | ||
} | ||
} |
46 changes: 0 additions & 46 deletions
46
browser-switch/src/main/java/com/braintreepayments/api/PublicBrowserSwitchLauncher.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
100 changes: 100 additions & 0 deletions
100
demo/src/main/java/com/braintreepayments/api/demo/LauncherActivityNoButton.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
package com.braintreepayments.api.demo | ||
|
||
import android.content.Intent | ||
import android.net.Uri | ||
import androidx.appcompat.app.AppCompatActivity | ||
import android.os.Bundle | ||
import android.widget.Toast | ||
import com.braintreepayments.api.BrowserSwitchOptions | ||
import com.braintreepayments.api.BrowserSwitchResult | ||
import com.braintreepayments.api.BrowserSwitchStatus | ||
import com.braintreepayments.api.BrowserSwitchLauncher | ||
import org.json.JSONException | ||
import org.json.JSONObject | ||
|
||
class LauncherActivityNoButton : AppCompatActivity() { | ||
|
||
private lateinit var launcher: BrowserSwitchLauncher | ||
private var browserSwitchHandled = false | ||
override fun onCreate(savedInstanceState: Bundle?) { | ||
super.onCreate(savedInstanceState) | ||
setContentView(R.layout.activity_launcher_no_button) | ||
launcher = | ||
BrowserSwitchLauncher(this) { browserSwitchResult -> | ||
onBrowserSwitchResult(browserSwitchResult) | ||
browserSwitchHandled = true | ||
} | ||
launcher.handleReturnToAppFromBrowser(this, 1, intent) | ||
|
||
if (!browserSwitchHandled) { | ||
launchBrowserSwitch() | ||
} | ||
} | ||
|
||
override fun onResume() { | ||
super.onResume() | ||
launcher.handleReturnToAppFromBrowser(this, 1, intent) | ||
} | ||
|
||
override fun onNewIntent(intent: Intent?) { | ||
super.onNewIntent(intent) | ||
if (intent != null) { | ||
setIntent(intent) | ||
} | ||
} | ||
private fun launchBrowserSwitch() { | ||
val metadata: JSONObject? = buildMetadataObject() | ||
val url: Uri = buildBrowserSwitchUrl() | ||
|
||
val browserSwitchOptions = BrowserSwitchOptions() | ||
.requestCode(1) | ||
.metadata(metadata) | ||
.url(url) | ||
.returnUrlScheme("launcher-activity-no-button") | ||
launcher.launch(browserSwitchOptions) | ||
} | ||
|
||
fun onBrowserSwitchResult(result: BrowserSwitchResult) { | ||
var resultText: String? = null | ||
var selectedColorText = "" | ||
val statusCode = result.status | ||
when (statusCode) { | ||
BrowserSwitchStatus.SUCCESS -> { | ||
resultText = "Browser Switch Successful" | ||
val returnUrl = result.deepLinkUrl | ||
if (returnUrl != null) { | ||
val color = returnUrl.getQueryParameter("color") | ||
selectedColorText = String.format("Selected color: %s", color) | ||
} | ||
} | ||
|
||
BrowserSwitchStatus.CANCELED -> resultText = "Browser Switch Cancelled by User" | ||
} | ||
var metadataOutput: String? = null | ||
val requestMetadata = result.requestMetadata | ||
if (requestMetadata != null) { | ||
try { | ||
val metadataValue = result.requestMetadata!!.getString("testKey") | ||
metadataOutput = String.format("%s=%s", "testKey", metadataValue) | ||
} catch (ignore: JSONException) { | ||
// do nothing | ||
} | ||
} | ||
Toast.makeText(this, resultText, Toast.LENGTH_LONG).show() | ||
} | ||
private fun buildMetadataObject(): JSONObject? { | ||
try { | ||
return JSONObject().put("testKey", "testValue") | ||
} catch (ignore: JSONException) { | ||
// do nothing | ||
} | ||
return null | ||
} | ||
|
||
private fun buildBrowserSwitchUrl(): Uri { | ||
val url = "https://braintree.github.io/popup-bridge-example/" + | ||
"this_launches_in_popup.html?popupBridgeReturnUrlPrefix=" + | ||
"launcher-activity-no-button" + "://" | ||
return Uri.parse(url) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.