Commit 18f228c6 authored by MozLando's avatar MozLando
Browse files

Merge #6127



6127: Closes #6147: Launch intent in third party app if URL scheme is not supported by engine r=psymoon a=rocketsroger
Co-authored-by: default avatarRoger Yang <royang@mozilla.com>
parents 945d1a47 e2be50e0
......@@ -54,7 +54,7 @@ class AppLinksFeature(
}
val doOpenApp = {
useCases.openAppLink(appIntent, failedToLaunchAction)
useCases.openAppLink(appIntent, failedToLaunchAction = failedToLaunchAction)
}
if (!session.private || fragmentManager == null) {
......
......@@ -11,6 +11,7 @@ import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.request.RequestInterceptor
import mozilla.components.feature.app.links.AppLinksUseCases.Companion.ENGINE_SUPPORTED_SCHEMES
/**
* This feature implements use cases for detecting and handling redirects to external apps. The user
......@@ -42,9 +43,7 @@ import mozilla.components.concept.engine.request.RequestInterceptor
class AppLinksInterceptor(
private val context: Context,
private val interceptLinkClicks: Boolean = false,
// list of scheme from https://searchfox.org/mozilla-central/source/netwerk/build/components.conf
private val engineSupportedSchemes: Set<String> = setOf("about", "data", "file", "ftp", "http",
"https", "moz-extension", "moz-safe-about", "resource", "view-source", "ws", "wss"),
private val engineSupportedSchemes: Set<String> = ENGINE_SUPPORTED_SCHEMES,
private val alwaysDeniedSchemes: Set<String> = setOf("javascript", "about"),
private val launchInApp: () -> Boolean = { false },
private val useCases: AppLinksUseCases = AppLinksUseCases(context, launchInApp),
......@@ -80,6 +79,7 @@ class AppLinksInterceptor(
val result = handleRedirect(redirect, uri, hasUserGesture)
if (redirect.isRedirect()) {
if (launchFromInterceptor && result is RequestInterceptor.InterceptionResponse.AppIntent) {
result.appIntent.flags = result.appIntent.flags or Intent.FLAG_ACTIVITY_NEW_TASK
useCases.openAppLink(result.appIntent)
}
......
......@@ -9,6 +9,7 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.net.isHttpOrHttps
......@@ -107,8 +108,8 @@ class AppLinksUseCases(
redirectData.resolveInfo == null -> null
includeHttpAppLinks && (ignoreDefaultBrowser ||
(redirectData.appIntent != null && isDefaultBrowser(redirectData.appIntent))) -> null
includeHttpAppLinks -> redirectData.appIntent
!launchInApp() && isAppIntentHttpOrHttps -> null
includeHttpAppLinks && isAppIntentHttpOrHttps -> redirectData.appIntent
!launchInApp() && ENGINE_SUPPORTED_SCHEMES.contains(Uri.parse(url).scheme) -> null
else -> redirectData.appIntent
}
......@@ -190,7 +191,11 @@ class AppLinksUseCases(
inner class OpenAppLinkRedirect internal constructor(
private val context: Context
) {
operator fun invoke(appIntent: Intent?, failedToLaunchAction: () -> Unit = {}) {
operator fun invoke(
appIntent: Intent?,
launchInNewTask: Boolean = true,
failedToLaunchAction: () -> Unit = {}
) {
appIntent?.let {
try {
val scheme = appIntent.data?.scheme
......@@ -198,6 +203,9 @@ class AppLinksUseCases(
return
}
if (launchInNewTask) {
it.flags = it.flags or Intent.FLAG_ACTIVITY_NEW_TASK
}
context.startActivity(it)
} catch (e: ActivityNotFoundException) {
failedToLaunchAction()
......@@ -245,5 +253,10 @@ class AppLinksUseCases(
companion object {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal var redirectCache: AppLinkRedirectCache? = null
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
// list of scheme from https://searchfox.org/mozilla-central/source/netwerk/build/components.conf
internal val ENGINE_SUPPORTED_SCHEMES: Set<String> = setOf("about", "data", "file", "ftp", "http",
"https", "moz-extension", "moz-safe-about", "resource", "view-source", "ws", "wss")
}
}
......@@ -18,9 +18,11 @@ import mozilla.components.support.test.mock
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
......@@ -141,7 +143,7 @@ class AppLinksFeatureTest {
userTapsOnSession(intentUrl, false)
verifyNoMoreInteractions(mockDialog)
verify(mockOpenRedirect).invoke(any(), any())
verify(mockOpenRedirect).invoke(any(), anyBoolean(), any())
}
@Test
......@@ -150,5 +152,6 @@ class AppLinksFeatureTest {
userTapsOnSession(intentUrl, true)
verify(mockDialog).show(eq(mockFragmentManager), anyString())
verify(mockOpenRedirect, never()).invoke(any(), anyBoolean(), any())
}
}
......@@ -16,6 +16,7 @@ import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
......@@ -299,7 +300,7 @@ class AppLinksInterceptorTest {
val response = appLinksInterceptor.onLoadRequest(mockEngineSession, webUrlWithAppLink, true, false)
assert(response is RequestInterceptor.InterceptionResponse.AppIntent)
verify(mockOpenRedirect).invoke(any(), any())
verify(mockOpenRedirect).invoke(any(), anyBoolean(), any())
}
@Test
......
......@@ -38,6 +38,7 @@ import java.io.File
class AppLinksUseCasesTest {
private val appUrl = "https://example.com"
private val appIntent = "intent://example.com"
private val appPackage = "com.example.app"
private val browserPackage = "com.browser"
private val testBrowserPackage = "com.current.browser"
......@@ -109,7 +110,7 @@ class AppLinksUseCasesTest {
}
@Test
fun `Will not redirect app link if browser option set to false`() {
fun `Will not redirect app link if browser option set to false and scheme is supported`() {
val context = createContext(appUrl to appPackage)
val subject = AppLinksUseCases(context, { false }, browserPackageNames = emptySet())
......@@ -120,6 +121,18 @@ class AppLinksUseCasesTest {
assertTrue(menuRedirect.isRedirect())
}
@Test
fun `Will redirect app link if browser option set to false and scheme is not supported`() {
val context = createContext(appIntent to appPackage)
val subject = AppLinksUseCases(context, { false }, browserPackageNames = emptySet())
val redirect = subject.interceptedAppLinkRedirect(appIntent)
assertTrue(redirect.isRedirect())
val menuRedirect = subject.appLinkRedirect(appIntent)
assertTrue(menuRedirect.isRedirect())
}
@Test
fun `A URL that matches only excluded packages is not an app link`() {
val context = createContext(appUrl to browserPackage)
......@@ -241,7 +254,7 @@ class AppLinksUseCasesTest {
var failedToLaunch = false
val failedAction = { failedToLaunch = true }
`when`(context.startActivity(any())).thenThrow(ActivityNotFoundException("failed"))
subject.openAppLink(redirect.appIntent, failedAction)
subject.openAppLink(redirect.appIntent, failedToLaunchAction = failedAction)
verify(context).startActivity(any())
assert(failedToLaunch)
......
......@@ -32,6 +32,7 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
......@@ -693,7 +694,7 @@ class ContextMenuCandidateTest {
mock(),
HitResult.UNKNOWN("https://www.otherexample.com"))
verify(openAppLinkRedirectMock, times(2)).invoke(any(), any())
verify(openAppLinkRedirectMock, times(2)).invoke(any(), anyBoolean(), any())
}
}
......
......@@ -260,7 +260,7 @@
<ID>UndocumentedPublicFunction:AppLinkRedirect.kt$AppLinkRedirect$fun hasFallback()</ID>
<ID>UndocumentedPublicFunction:AppLinkRedirect.kt$AppLinkRedirect$fun isRedirect()</ID>
<ID>UndocumentedPublicFunction:AppLinksUseCases.kt$AppLinksUseCases.GetAppLinkRedirect$operator fun invoke(url: String): AppLinkRedirect</ID>
<ID>UndocumentedPublicFunction:AppLinksUseCases.kt$AppLinksUseCases.OpenAppLinkRedirect$operator fun invoke(appIntent: Intent?, failedToLaunchAction: () -> Unit = {})</ID>
<ID>UndocumentedPublicFunction:AppLinksUseCases.kt$AppLinksUseCases.OpenAppLinkRedirect$operator fun invoke( appIntent: Intent?, launchInNewTask: Boolean = true, failedToLaunchAction: () -> Unit = {} )</ID>
<ID>UndocumentedPublicFunction:Arguments.kt$Arguments$fun assertIsNotBlank(input: String, exceptionIdentifier: String)</ID>
<ID>UndocumentedPublicFunction:Arguments.kt$Arguments$fun assertIsValidUserAgent(userAgent: String)</ID>
<ID>UndocumentedPublicFunction:AutoPushFeature.kt$DeliveryManager$fun serviceForChannelId(channelId: String): PushType</ID>
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment