Skip to content

Commit

Permalink
[Connect SDK] Add support for non-http/https pop-ups (ex. mailto) (#9679
Browse files Browse the repository at this point in the history
)

* Add pop-up supoort

# Conflicts:
#	connect/src/main/java/com/stripe/android/connect/webview/StripeConnectWebViewContainerController.kt

# Conflicts:
#	connect/src/main/java/com/stripe/android/connect/webview/StripeConnectWebViewContainer.kt
#	connect/src/main/java/com/stripe/android/connect/webview/StripeConnectWebViewContainerController.kt

* Fix lint

* Make class internal

* Add tests, add intent interface

* Cleanup merge conflicts

* Update function with todos

* Fix tests

* PR feedback

* PR feedback in Android

* Add pop-up supoort

# Conflicts:
#	connect/src/main/java/com/stripe/android/connect/webview/StripeConnectWebViewClient.kt

* Fix lint

* Add tests, add intent interface

* Add initial mailto/non-https support

* Fix merge conflicts

* Fixup launcher

* Delete unneeded web chrome client

* Fix/add tests

* Move to string res

* Fix test
  • Loading branch information
simond-stripe authored Nov 20, 2024
1 parent 7b8f59a commit cb051e9
Show file tree
Hide file tree
Showing 7 changed files with 80 additions and 16 deletions.
1 change: 1 addition & 0 deletions connect/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<string name="stripe_downloading_file">Downloading file</string>
<string name="stripe_download_complete">Download complete</string>
<string name="stripe_unable_to_download_file">Unable to download file</string>
<string name="stripe_failed_to_open_url">Failed to open URL: %s</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,13 @@ internal class StripeConnectWebViewContainerController(
stripeIntentLauncher.launchSecureExternalWebTab(context, url)
true // block the request since we're opening it in a secure external tab
} else {
// TODO - support non-http/https schemes.
logger.debug("(StripeConnectWebViewClient) Received unsupported pop-up request: $url")
true // block the request as it's currently unsupported
logger.debug("(StripeConnectWebViewClient) Opening non-http/https pop-up request: $url")
if (url.scheme.equals("mailto", ignoreCase = true)) {
stripeIntentLauncher.launchEmailLink(context, url)
} else {
stripeIntentLauncher.launchUrlWithSystemHandler(context, url)
}
true // block the request since we're opening it via the system handler
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import kotlinx.coroutines.launch
internal class StripeDownloadListener(
private val context: Context,
private val stripeDownloadManager: StripeDownloadManager = StripeDownloadManagerImpl(context),
private val stripeToastManager: StripeToastManager = StripeToastManagerImpl(context),
private val stripeToastManager: StripeToastManager = StripeToastManagerImpl(),
private val ioScope: CoroutineScope = CoroutineScope(Dispatchers.IO),
) : DownloadListener {

Expand Down Expand Up @@ -55,11 +55,11 @@ internal class StripeDownloadListener(
}

private fun showErrorToast() {
stripeToastManager.showToast(context.getString(R.string.stripe_unable_to_download_file))
stripeToastManager.showToast(context, context.getString(R.string.stripe_unable_to_download_file))
}

private fun showOpenFileToast() {
stripeToastManager.showToast(context.getString(R.string.stripe_download_complete))
stripeToastManager.showToast(context, context.getString(R.string.stripe_download_complete))
}

internal companion object {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,65 @@
package com.stripe.android.connect.webview

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.browser.customtabs.CustomTabsIntent
import com.stripe.android.connect.R
import com.stripe.android.core.BuildConfig
import com.stripe.android.core.Logger

internal interface StripeIntentLauncher {
/**
* Launches [uri] in a secure external Android Custom Tab.
*/
fun launchSecureExternalWebTab(context: Context, uri: Uri)

/**
* Launches a uri with a mailto scheme.
*/
fun launchEmailLink(context: Context, uri: Uri)

/**
* Launches [uri] with the system handler, allowing the system to choose how to open it.
*/
fun launchUrlWithSystemHandler(context: Context, uri: Uri)
}

internal class StripeIntentLauncherImpl : StripeIntentLauncher {
internal class StripeIntentLauncherImpl(
private val toastManagerImpl: StripeToastManagerImpl = StripeToastManagerImpl(),
private val logger: Logger = Logger.getInstance(enableLogging = BuildConfig.DEBUG)
) : StripeIntentLauncher {

override fun launchSecureExternalWebTab(context: Context, uri: Uri) {
val customTabsIntent = CustomTabsIntent.Builder().build()
customTabsIntent.launchUrl(context, uri)
}

override fun launchUrlWithSystemHandler(context: Context, uri: Uri) {
val intent = Intent(Intent.ACTION_VIEW, uri).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
logger.error("Failed to open URL with system handler: ${e.message}")
toastManagerImpl.showToast(context, context.getString(R.string.stripe_failed_to_open_url, uri.toString()))
}
}

override fun launchEmailLink(context: Context, uri: Uri) {
val intent = Intent(Intent.ACTION_SENDTO, uri).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}

try {
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
// log an error and fall back to a generic system handler
logger.error("Failed to open URL with email handler: ${e.message}")
launchUrlWithSystemHandler(context, uri)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ import kotlinx.coroutines.launch
* Provides an interface for various download and file operations. Useful for mocking in tests.
*/
internal interface StripeToastManager {
fun showToast(toastString: String)
fun showToast(context: Context, toastString: String)
}

internal class StripeToastManagerImpl(
private val context: Context,
private val scope: CoroutineScope = MainScope()
) : StripeToastManager {
override fun showToast(toastString: String) {
override fun showToast(context: Context, toastString: String) {
scope.launch {
Toast.makeText(context, toastString, Toast.LENGTH_LONG).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import org.mockito.kotlin.mock
import org.mockito.kotlin.verify
import org.robolectric.RobolectricTestRunner
import kotlin.test.assertFalse
import kotlin.test.assertTrue

@OptIn(PrivateBetaConnectSDK::class)
@RunWith(RobolectricTestRunner::class)
Expand Down Expand Up @@ -71,18 +72,31 @@ class StripeConnectWebViewContainerControllerTest {

val result = controller.shouldOverrideUrlLoading(mockContext, mockRequest)

assert(result)
assertTrue(result)
verify(mockStripeIntentLauncher).launchSecureExternalWebTab(mockContext, uri)
}

@Test
fun `shouldOverrideUrlLoading blocks url loading and returns true for unsupported schemes`() {
fun `shouldOverrideUrlLoading opens email client for mailto urls`() {
val uri = Uri.parse("mailto://example@stripe.com")
val mockRequest = mock<WebResourceRequest> {
on { url } doReturn uri
}

val result = controller.shouldOverrideUrlLoading(mockContext, mockRequest)
assert(result)
verify(mockStripeIntentLauncher).launchEmailLink(mockContext, uri)
assertTrue(result)
}

@Test
fun `shouldOverrideUrlLoading opens system launcher for non-http urls`() {
val uri = Uri.parse("stripe://example@stripe.com")
val mockRequest = mock<WebResourceRequest> {
on { url } doReturn uri
}

val result = controller.shouldOverrideUrlLoading(mockContext, mockRequest)
verify(mockStripeIntentLauncher).launchUrlWithSystemHandler(mockContext, uri)
assertTrue(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class StripeDownloadListenerTest {
testScope.testScheduler.advanceUntilIdle()

verify(stripeDownloadManager).enqueueDownload(url, contentDisposition, mimeType)
verify(stripeToastManager).showToast(any())
verify(stripeToastManager).showToast(any(), any())
}

@Test
Expand All @@ -74,7 +74,7 @@ class StripeDownloadListenerTest {
testScope.testScheduler.advanceUntilIdle()

verifyNoInteractions(stripeDownloadManager)
verify(stripeToastManager).showToast(any())
verify(stripeToastManager).showToast(any(), any())
}

@Test
Expand All @@ -90,6 +90,6 @@ class StripeDownloadListenerTest {
stripeDownloadListener.onDownloadStart(url, userAgent, contentDisposition, mimeType, contentLength)
testScope.testScheduler.advanceUntilIdle()

verify(stripeToastManager).showToast(any())
verify(stripeToastManager).showToast(any(), any())
}
}

0 comments on commit cb051e9

Please sign in to comment.