1
0
Fork 0

For #5334: added private custom tab processor (#6845)

* For #5334: added private custom tab processor

* For #5334 - Fixes up IntentReceiverActivity for handling intents

* For 5334: update styling for private custom tabbs

* For 5334: update tests to account for new processors

Note that two are still failing. These appear to be true failures, and will be corrected in a later commit.

* For 5334: fixes bug introduced by changes to IntentReceiverActivity

RCA: intent className and extra were previously set based on which processors matched, not which successfully processed. This patch reintroduces that behavior.

* For 5334: add tests for custom tabs processing
master
Severin Rudie 2019-12-06 10:57:54 -08:00 committed by GitHub
parent 4fb26a0601
commit 5f393bd5d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 61 deletions

View File

@ -10,14 +10,52 @@ import android.os.Bundle
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import mozilla.components.feature.intent.processing.TabIntentProcessor
import mozilla.components.feature.intent.processing.IntentProcessor
import mozilla.components.support.utils.Browsers
import org.mozilla.fenix.components.IntentProcessors
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor
enum class IntentProcessorType {
EXTERNAL_APP, NEW_TAB, OTHER;
/**
* The destination activity based on this intent
*/
val activityClassName: String
get() = when (this) {
EXTERNAL_APP -> ExternalAppBrowserActivity::class.java.name
NEW_TAB -> HomeActivity::class.java.name
OTHER -> HomeActivity::class.java.name
}
/**
* Should this intent automatically navigate to the browser?
*/
fun shouldOpenToBrowser(intent: Intent): Boolean = when (this) {
EXTERNAL_APP -> true
NEW_TAB -> intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == 0
OTHER -> false
}
}
/**
* Classifies the [IntentType] based on the [IntentProcessor] that handled the [Intent]
*/
fun IntentProcessor?.getType(intentProcessors: IntentProcessors): IntentProcessorType {
return when {
intentProcessors.externalAppIntentProcessors.contains(this) ||
intentProcessors.customTabIntentProcessor == this ||
intentProcessors.privateCustomTabIntentProcessor == this -> IntentProcessorType.EXTERNAL_APP
intentProcessors.intentProcessor == this ||
intentProcessors.privateIntentProcessor == this -> IntentProcessorType.NEW_TAB
else -> IntentProcessorType.OTHER
}
}
/**
* Processes incoming intents and sends them to the corresponding activity.
*/
@ -47,49 +85,35 @@ class IntentReceiverActivity : Activity() {
))
}
val tabIntentProcessor = if (settings().openLinksInAPrivateTab) {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
components.intentProcessors.privateIntentProcessor
} else {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
components.intentProcessors.intentProcessor
}
val modeDependentProcessors = if (settings().openLinksInAPrivateTab) {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.PRIVATE))
listOf(
components.intentProcessors.privateCustomTabIntentProcessor,
components.intentProcessors.privateIntentProcessor
)
} else {
components.analytics.metrics.track(Event.OpenedLink(Event.OpenedLink.Mode.NORMAL))
listOf(
components.intentProcessors.customTabIntentProcessor,
components.intentProcessors.intentProcessor
)
}
val intentProcessors = components.intentProcessors.externalAppIntentProcessors +
tabIntentProcessor +
modeDependentProcessors +
NewTabShortcutIntentProcessor()
// Call process for side effects, short on the first that returns true
intentProcessors.any { it.process(intent) }
setIntentActivity(intent, tabIntentProcessor)
val intentProcessorType = intentProcessors
.firstOrNull { it.matches(intent) }
.getType(components.intentProcessors)
intent.setClassName(applicationContext, intentProcessorType.activityClassName)
intent.putExtra(HomeActivity.OPEN_TO_BROWSER, intentProcessorType.shouldOpenToBrowser(intent))
startActivity(intent)
finish()
}
/**
* Sets the activity that this [intent] will launch.
*/
private fun setIntentActivity(intent: Intent, tabIntentProcessor: TabIntentProcessor) {
val openToBrowser = when {
components.intentProcessors.externalAppIntentProcessors.any { it.matches(intent) } -> {
intent.setClassName(applicationContext, ExternalAppBrowserActivity::class.java.name)
true
}
tabIntentProcessor.matches(intent) -> {
intent.setClassName(applicationContext, HomeActivity::class.java.name)
// This Intent was launched from history (recent apps). Android will redeliver the
// original Intent (which might be a VIEW intent). However if there's no active browsing
// session then we do not want to re-process the Intent and potentially re-open a website
// from a session that the user already "erased".
intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == 0
}
else -> {
intent.setClassName(applicationContext, HomeActivity::class.java.name)
false
}
}
intent.putExtra(HomeActivity.OPEN_TO_BROWSER, openToBrowser)
}
}

View File

@ -44,9 +44,3 @@ class DefaultBrowsingModeManager(
modeDidChange(value)
}
}
class CustomTabBrowsingModeManager : BrowsingModeManager {
override var mode
get() = BrowsingMode.Normal
set(_) { /* no-op */ }
}

View File

@ -45,7 +45,11 @@ class IntentProcessors(
}
val customTabIntentProcessor by lazy {
CustomTabIntentProcessor(sessionManager, sessionUseCases.loadUrl, context.resources)
CustomTabIntentProcessor(sessionManager, sessionUseCases.loadUrl, context.resources, isPrivate = false)
}
val privateCustomTabIntentProcessor by lazy {
CustomTabIntentProcessor(sessionManager, sessionUseCases.loadUrl, context.resources, isPrivate = true)
}
val externalAppIntentProcessors by lazy {
@ -58,8 +62,7 @@ class IntentProcessors(
apiKey = BuildConfig.DIGITAL_ASSET_LINKS_TOKEN,
store = customTabsStore
),
WebAppIntentProcessor(sessionManager, sessionUseCases.loadUrl, ManifestStorage(context)),
customTabIntentProcessor
WebAppIntentProcessor(sessionManager, sessionUseCases.loadUrl, ManifestStorage(context))
)
}
}

View File

@ -27,7 +27,8 @@ class CustomTabsIntegration(
sessionId: String,
activity: Activity,
engineLayout: View,
onItemTapped: (ToolbarMenu.Item) -> Unit = {}
onItemTapped: (ToolbarMenu.Item) -> Unit = {},
isPrivate: Boolean
) : LifecycleAwareFeature, UserInteractionHandler {
init {
@ -75,6 +76,15 @@ class CustomTabsIntegration(
)!!
)
}
// If in private mode, override toolbar background to use private color
// See #5334
if (isPrivate) {
toolbar.background = AppCompatResources.getDrawable(
activity,
R.drawable.toolbar_background
)
}
}
private val customTabToolbarMenu by lazy {

View File

@ -14,11 +14,8 @@ import mozilla.components.support.utils.SafeIntent
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.CustomTabBrowsingModeManager
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.theme.CustomTabThemeManager
import java.security.InvalidParameterException
/**
@ -60,11 +57,6 @@ open class ExternalAppBrowserActivity : HomeActivity() {
}
}
final override fun createBrowsingModeManager(initialMode: BrowsingMode) =
CustomTabBrowsingModeManager()
final override fun createThemeManager() = CustomTabThemeManager()
override fun onDestroy() {
super.onDestroy()

View File

@ -28,6 +28,7 @@ import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R
import org.mozilla.fenix.browser.BaseBrowserFragment
import org.mozilla.fenix.browser.CustomTabContextMenuCandidate
@ -67,7 +68,8 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
sessionId = customTabSessionId,
activity = activity,
engineLayout = view.swipeRefresh,
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) }
onItemTapped = { browserInteractor.onBrowserToolbarMenuItemTapped(it) },
isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate
),
owner = this,
view = view)

View File

@ -121,9 +121,3 @@ class DefaultThemeManager(
}
}
}
class CustomTabThemeManager : ThemeManager() {
override var currentTheme
get() = BrowsingMode.Normal
set(_) { /* noop */ }
}

View File

@ -10,11 +10,13 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.`when`
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor
@ -37,6 +39,8 @@ class IntentReceiverActivityTest {
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.intentProcessor.matches(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.matches(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
@ -55,6 +59,7 @@ class IntentReceiverActivityTest {
intent.action = NewTabShortcutIntentProcessor.ACTION_OPEN_PRIVATE_TAB
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(false)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
@ -74,6 +79,7 @@ class IntentReceiverActivityTest {
intent.action = NewTabShortcutIntentProcessor.ACTION_OPEN_TAB
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
@ -91,6 +97,7 @@ class IntentReceiverActivityTest {
val intent = Intent()
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
@ -107,6 +114,7 @@ class IntentReceiverActivityTest {
val intent = Intent()
`when`(testContext.components.intentProcessors.privateIntentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.privateCustomTabIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
@ -122,6 +130,7 @@ class IntentReceiverActivityTest {
val intent = Intent()
`when`(testContext.components.intentProcessors.intentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(false)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
@ -131,4 +140,44 @@ class IntentReceiverActivityTest {
verify(testContext.components.intentProcessors.privateIntentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.intentProcessor).process(intent)
}
@Test
fun `process custom tab intent`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = false
val intent = Intent()
`when`(testContext.components.intentProcessors.customTabIntentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.customTabIntentProcessor.matches(intent)).thenReturn(true)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
// Not using mockk here because process is a suspend function
// and mockito makes this easier to read.
verify(testContext.components.intentProcessors.privateIntentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.customTabIntentProcessor).process(intent)
assertEquals(ExternalAppBrowserActivity::class.java.name, intent.component!!.className)
assertTrue(intent.getBooleanExtra(HomeActivity.OPEN_TO_BROWSER, false))
}
@Test
fun `process private custom tab intent`() = runBlockingTest {
testContext.settings().openLinksInAPrivateTab = true
val intent = Intent()
`when`(testContext.components.intentProcessors.privateCustomTabIntentProcessor.process(intent)).thenReturn(true)
`when`(testContext.components.intentProcessors.privateCustomTabIntentProcessor.matches(intent)).thenReturn(true)
val activity = Robolectric.buildActivity(IntentReceiverActivity::class.java, intent).get()
activity.processIntent(intent)
// Not using mockk here because process is a suspend function
// and mockito makes this easier to read.
verify(testContext.components.intentProcessors.intentProcessor, never()).process(intent)
verify(testContext.components.intentProcessors.privateCustomTabIntentProcessor).process(intent)
assertEquals(ExternalAppBrowserActivity::class.java.name, intent.component!!.className)
assertTrue(intent.getBooleanExtra(HomeActivity.OPEN_TO_BROWSER, false))
}
}

View File

@ -32,6 +32,8 @@ class TestComponents(private val context: Context) : Components(context) {
`when`(processors.externalAppIntentProcessors).thenReturn(emptyList())
`when`(processors.privateIntentProcessor).thenReturn(mock())
`when`(processors.intentProcessor).thenReturn(mock())
`when`(processors.customTabIntentProcessor).thenReturn(mock())
`when`(processors.privateCustomTabIntentProcessor).thenReturn(mock())
processors
}
override val analytics by lazy { Analytics(context) }