Extract intent processors from HomeActivity (#4884)
parent
4bd94f0f9b
commit
9f154dc3a0
|
@ -6,8 +6,6 @@ package org.mozilla.fenix
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
@ -28,7 +26,6 @@ import mozilla.components.browser.search.SearchEngine
|
||||||
import mozilla.components.browser.session.Session
|
import mozilla.components.browser.session.Session
|
||||||
import mozilla.components.browser.session.SessionManager
|
import mozilla.components.browser.session.SessionManager
|
||||||
import mozilla.components.concept.engine.EngineView
|
import mozilla.components.concept.engine.EngineView
|
||||||
import mozilla.components.lib.crash.Crash
|
|
||||||
import mozilla.components.support.base.feature.BackHandler
|
import mozilla.components.support.base.feature.BackHandler
|
||||||
import mozilla.components.support.ktx.kotlin.isUrl
|
import mozilla.components.support.ktx.kotlin.isUrl
|
||||||
import mozilla.components.support.ktx.kotlin.toNormalizedUrl
|
import mozilla.components.support.ktx.kotlin.toNormalizedUrl
|
||||||
|
@ -44,6 +41,11 @@ import org.mozilla.fenix.components.metrics.SentryBreadcrumbsRecorder
|
||||||
import org.mozilla.fenix.ext.components
|
import org.mozilla.fenix.ext.components
|
||||||
import org.mozilla.fenix.ext.getRootView
|
import org.mozilla.fenix.ext.getRootView
|
||||||
import org.mozilla.fenix.ext.nav
|
import org.mozilla.fenix.ext.nav
|
||||||
|
import org.mozilla.fenix.home.intent.CrashReporterIntentProcessor
|
||||||
|
import org.mozilla.fenix.home.intent.DeepLinkIntentProcessor
|
||||||
|
import org.mozilla.fenix.home.intent.OpenBrowserIntentProcessor
|
||||||
|
import org.mozilla.fenix.home.intent.SpeechProcessingIntentProcessor
|
||||||
|
import org.mozilla.fenix.home.intent.StartSearchIntentProcessor
|
||||||
import org.mozilla.fenix.share.ShareFragment
|
import org.mozilla.fenix.share.ShareFragment
|
||||||
import org.mozilla.fenix.theme.DefaultThemeManager
|
import org.mozilla.fenix.theme.DefaultThemeManager
|
||||||
import org.mozilla.fenix.theme.ThemeManager
|
import org.mozilla.fenix.theme.ThemeManager
|
||||||
|
@ -61,6 +63,15 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
|
||||||
supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.container) as NavHostFragment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val externalSourceIntentProcessors by lazy {
|
||||||
|
listOf(
|
||||||
|
SpeechProcessingIntentProcessor(this),
|
||||||
|
StartSearchIntentProcessor(components.analytics.metrics),
|
||||||
|
DeepLinkIntentProcessor(this),
|
||||||
|
OpenBrowserIntentProcessor(this, ::getIntentSessionId)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
final override fun onCreate(savedInstanceState: Bundle?) {
|
final override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
@ -105,8 +116,10 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
|
||||||
*/
|
*/
|
||||||
final override fun onNewIntent(intent: Intent?) {
|
final override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
handleCrashIfNecessary(intent)
|
intent ?: return
|
||||||
handleOpenedFromExternalSourceIfNecessary(intent)
|
|
||||||
|
val intentProcessors = listOf(CrashReporterIntentProcessor()) + externalSourceIntentProcessors
|
||||||
|
intentProcessors.any { it.process(intent, navHost.navController, this.intent) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -162,86 +175,7 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
||||||
handleOpenedFromExternalSourceIfNecessary(intent)
|
externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) }
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleCrashIfNecessary(intent: Intent?) {
|
|
||||||
if (intent != null && Crash.isCrashIntent(intent)) {
|
|
||||||
openToCrashReporter(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun openToCrashReporter(intent: Intent) {
|
|
||||||
val directions = NavGraphDirections.actionGlobalCrashReporter(intent)
|
|
||||||
navHost.navController.navigate(directions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleOpenedFromExternalSourceIfNecessary(intent: Intent?) {
|
|
||||||
if (intent?.extras?.getBoolean(OPEN_TO_BROWSER_AND_LOAD) == true) {
|
|
||||||
this.intent.putExtra(OPEN_TO_BROWSER_AND_LOAD, false)
|
|
||||||
openToBrowserAndLoad(
|
|
||||||
intent.getStringExtra(
|
|
||||||
IntentReceiverActivity.SPEECH_PROCESSING
|
|
||||||
), true, BrowserDirection.FromGlobal, forceSearch = true
|
|
||||||
)
|
|
||||||
return
|
|
||||||
} else if (intent?.extras?.getBoolean(OPEN_TO_SEARCH) == true) {
|
|
||||||
this.intent.putExtra(OPEN_TO_SEARCH, false)
|
|
||||||
components.analytics.metrics.track(Event.SearchWidgetNewTabPressed)
|
|
||||||
navHost.navController.nav(null, NavGraphDirections.actionGlobalSearch(null, true))
|
|
||||||
return
|
|
||||||
} else if (intent?.scheme == "fenix") {
|
|
||||||
intent.data?.let { handleDeepLink(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (intent?.extras?.getBoolean(OPEN_TO_BROWSER) != true) return
|
|
||||||
|
|
||||||
this.intent.putExtra(OPEN_TO_BROWSER, false)
|
|
||||||
|
|
||||||
openToBrowser(BrowserDirection.FromGlobal, getIntentSessionId(intent.toSafeIntent()))
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("ComplexMethod")
|
|
||||||
private fun handleDeepLink(uri: Uri) {
|
|
||||||
val link = uri.host
|
|
||||||
|
|
||||||
// Handle links that require more than just simple navigation
|
|
||||||
when (link) {
|
|
||||||
"enable_private_browsing" -> {
|
|
||||||
navHost.navController.navigate(NavGraphDirections.actionGlobalHomeFragment())
|
|
||||||
browsingModeManager.mode = BrowsingMode.Private
|
|
||||||
}
|
|
||||||
"make_default_browser" -> {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { return }
|
|
||||||
val settingsIntent = Intent(
|
|
||||||
android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS
|
|
||||||
)
|
|
||||||
startActivity(settingsIntent)
|
|
||||||
}
|
|
||||||
"open" -> {
|
|
||||||
uri.getQueryParameter("url")?.let {
|
|
||||||
load(
|
|
||||||
searchTermOrURL = it,
|
|
||||||
newTab = true,
|
|
||||||
engine = null,
|
|
||||||
forceSearch = false
|
|
||||||
)
|
|
||||||
navHost.navController.navigate(NavGraphDirections.actionGlobalBrowser(null))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val directions = when (link) {
|
|
||||||
"home" -> NavGraphDirections.actionGlobalHomeFragment()
|
|
||||||
"settings" -> NavGraphDirections.actionGlobalSettingsFragment()
|
|
||||||
"turn_on_sync" -> NavGraphDirections.actionGlobalTurnOnSync()
|
|
||||||
"settings_search_engine" -> NavGraphDirections.actionGlobalSearchEngineFragment()
|
|
||||||
"settings_accessibility" -> NavGraphDirections.actionGlobalAccessibilityFragment()
|
|
||||||
"settings_delete_browsing_data" -> NavGraphDirections.actionGlobalDeleteBrowsingDataFragment()
|
|
||||||
else -> return
|
|
||||||
}
|
|
||||||
|
|
||||||
navHost.navController.navigate(directions)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun getIntentSessionId(intent: SafeIntent): String? = null
|
protected open fun getIntentSessionId(intent: SafeIntent): String? = null
|
||||||
|
@ -264,11 +198,7 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback
|
||||||
sessionObserver = subscribeToSessions()
|
sessionObserver = subscribeToSessions()
|
||||||
|
|
||||||
with(navHost.navController) {
|
with(navHost.navController) {
|
||||||
if (currentDestination?.id == R.id.browserFragment || popBackStack(
|
if (currentDestination?.id == R.id.browserFragment || popBackStack(R.id.browserFragment, false)) return
|
||||||
R.id.browserFragment,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
) return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@IdRes val fragmentId = if (from.fragmentId != 0) from.fragmentId else null
|
@IdRes val fragmentId = if (from.fragmentId != 0) from.fragmentId else null
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import mozilla.components.lib.crash.Crash
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the app crashes, the user has the option to report it.
|
||||||
|
* Reporting fires an intent to the main activity which is handled here.
|
||||||
|
*/
|
||||||
|
class CrashReporterIntentProcessor : HomeIntentProcessor {
|
||||||
|
|
||||||
|
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||||
|
return if (Crash.isCrashIntent(intent)) {
|
||||||
|
openToCrashReporter(intent, navController)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openToCrashReporter(intent: Intent, navController: NavController) {
|
||||||
|
val directions = NavGraphDirections.actionGlobalCrashReporter(intent)
|
||||||
|
navController.navigate(directions)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,75 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Build.VERSION.SDK_INT
|
||||||
|
import android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
|
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep links in the form of `fenix://host` open different parts of the app.
|
||||||
|
*/
|
||||||
|
class DeepLinkIntentProcessor(
|
||||||
|
private val activity: HomeActivity
|
||||||
|
) : HomeIntentProcessor {
|
||||||
|
|
||||||
|
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||||
|
return if (intent.scheme == "fenix") {
|
||||||
|
intent.data?.let { handleDeepLink(it, navController) }
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDeepLink(deepLink: Uri, navController: NavController) {
|
||||||
|
handleDeepLinkSideEffects(deepLink)
|
||||||
|
|
||||||
|
val directions = when (deepLink.host) {
|
||||||
|
"home", "enable_private_browsing" -> NavGraphDirections.actionGlobalHomeFragment()
|
||||||
|
"settings" -> NavGraphDirections.actionGlobalSettingsFragment()
|
||||||
|
"turn_on_sync" -> NavGraphDirections.actionGlobalTurnOnSync()
|
||||||
|
"settings_search_engine" -> NavGraphDirections.actionGlobalSearchEngineFragment()
|
||||||
|
"settings_accessibility" -> NavGraphDirections.actionGlobalAccessibilityFragment()
|
||||||
|
"settings_delete_browsing_data" -> NavGraphDirections.actionGlobalDeleteBrowsingDataFragment()
|
||||||
|
else -> return
|
||||||
|
}
|
||||||
|
|
||||||
|
navController.navigate(directions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle links that require more than just simple navigation.
|
||||||
|
*/
|
||||||
|
private fun handleDeepLinkSideEffects(deepLink: Uri) {
|
||||||
|
when (deepLink.host) {
|
||||||
|
"enable_private_browsing" -> {
|
||||||
|
activity.browsingModeManager.mode = BrowsingMode.Private
|
||||||
|
}
|
||||||
|
"make_default_browser" -> {
|
||||||
|
if (SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
val settingsIntent = Intent(ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
|
||||||
|
activity.startActivity(settingsIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"open" -> {
|
||||||
|
deepLink.getQueryParameter("url")?.let { searchTermOrUrl ->
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrUrl,
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromGlobal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processor for Android intents received in [org.mozilla.fenix.HomeActivity].
|
||||||
|
*/
|
||||||
|
interface HomeIntentProcessor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes the given [intent]. May add properties to [out].
|
||||||
|
*
|
||||||
|
* @param intent The intent to process.
|
||||||
|
* @param navController Controller to navigate between fragments.
|
||||||
|
* @param out Intent to mutate.
|
||||||
|
* @return True if the intent was processed, otherwise false.
|
||||||
|
*/
|
||||||
|
fun process(intent: Intent, navController: NavController, out: Intent): Boolean
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import mozilla.components.support.utils.SafeIntent
|
||||||
|
import mozilla.components.support.utils.toSafeIntent
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The [org.mozilla.fenix.IntentReceiverActivity] may set the [HomeActivity.OPEN_TO_BROWSER] flag
|
||||||
|
* when the browser should be opened in response to an intent.
|
||||||
|
*/
|
||||||
|
class OpenBrowserIntentProcessor(
|
||||||
|
private val activity: HomeActivity,
|
||||||
|
private val getIntentSessionId: (SafeIntent) -> String?
|
||||||
|
) : HomeIntentProcessor {
|
||||||
|
|
||||||
|
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||||
|
return if (intent.extras?.getBoolean(HomeActivity.OPEN_TO_BROWSER) == true) {
|
||||||
|
out.putExtra(HomeActivity.OPEN_TO_BROWSER, false)
|
||||||
|
|
||||||
|
activity.openToBrowser(BrowserDirection.FromGlobal, getIntentSessionId(intent.toSafeIntent()))
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.IntentReceiverActivity
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The search widget has a microphone button to let users search with their voice.
|
||||||
|
* Once the search is complete then a new search should be started.
|
||||||
|
*/
|
||||||
|
class SpeechProcessingIntentProcessor(
|
||||||
|
private val activity: HomeActivity
|
||||||
|
) : HomeIntentProcessor {
|
||||||
|
|
||||||
|
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||||
|
return if (intent.extras?.getBoolean(HomeActivity.OPEN_TO_BROWSER_AND_LOAD) == true) {
|
||||||
|
out.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false)
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = intent.getStringExtra(IntentReceiverActivity.SPEECH_PROCESSING).orEmpty(),
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromGlobal,
|
||||||
|
forceSearch = true
|
||||||
|
)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.mozilla.fenix.ext.nav
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When the search widget is tapped, Fenix should open to the search fragment.
|
||||||
|
*/
|
||||||
|
class StartSearchIntentProcessor(
|
||||||
|
private val metrics: MetricController
|
||||||
|
) : HomeIntentProcessor {
|
||||||
|
|
||||||
|
override fun process(intent: Intent, navController: NavController, out: Intent): Boolean {
|
||||||
|
return if (intent.extras?.getBoolean(HomeActivity.OPEN_TO_SEARCH) == true) {
|
||||||
|
out.putExtra(HomeActivity.OPEN_TO_SEARCH, false)
|
||||||
|
metrics.track(Event.SearchWidgetNewTabPressed)
|
||||||
|
|
||||||
|
val directions = NavGraphDirections.actionGlobalSearch(sessionId = null, showShortcutEnginePicker = true)
|
||||||
|
navController.nav(null, directions)
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.Called
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@ObsoleteCoroutinesApi
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class CrashReporterIntentProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process blank intents`() {
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
CrashReporterIntentProcessor().process(Intent(), navController, out)
|
||||||
|
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process crash intents`() {
|
||||||
|
val navController: NavController = mockk(relaxed = true)
|
||||||
|
val out: Intent = mockk()
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra("mozilla.components.lib.crash.CRASH", mockk<Bundle>())
|
||||||
|
}
|
||||||
|
CrashReporterIntentProcessor().process(intent, navController, out)
|
||||||
|
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalCrashReporter(intent)) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,156 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.Called
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@ObsoleteCoroutinesApi
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class DeepLinkIntentProcessorTest {
|
||||||
|
|
||||||
|
private lateinit var activity: HomeActivity
|
||||||
|
private lateinit var navController: NavController
|
||||||
|
private lateinit var out: Intent
|
||||||
|
private lateinit var processor: DeepLinkIntentProcessor
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
activity = mockk(relaxed = true)
|
||||||
|
navController = mockk(relaxed = true)
|
||||||
|
out = mockk()
|
||||||
|
processor = DeepLinkIntentProcessor(activity)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process blank intents`() {
|
||||||
|
assertFalse(processor.process(Intent(), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `return true if scheme is fenix`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://test"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process home deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://home"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalHomeFragment()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process settings deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://settings"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalSettingsFragment()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process turn_on_sync deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://turn_on_sync"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalTurnOnSync()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process settings_search_engine deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://settings_search_engine"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalSearchEngineFragment()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process settings_accessibility deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://settings_accessibility"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalAccessibilityFragment()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process settings_delete_browsing_data deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://settings_delete_browsing_data"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalDeleteBrowsingDataFragment()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process enable_private_browsing deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://enable_private_browsing"), navController, out))
|
||||||
|
|
||||||
|
verify { activity.browsingModeManager.mode = BrowsingMode.Private }
|
||||||
|
verify { navController.navigate(NavGraphDirections.actionGlobalHomeFragment()) }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process open deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://open"), navController, out))
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
|
||||||
|
assertTrue(processor.process(testIntent("fenix://open?url=test"), navController, out))
|
||||||
|
|
||||||
|
verify {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
"test",
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromGlobal
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process make_default_browser deep link`() {
|
||||||
|
assertTrue(processor.process(testIntent("fenix://make_default_browser"), navController, out))
|
||||||
|
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun testIntent(uri: String) = Intent("", uri.toUri())
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.Called
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@ObsoleteCoroutinesApi
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class OpenBrowserIntentProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process blank intents`() {
|
||||||
|
val activity: HomeActivity = mockk()
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
val processor = OpenBrowserIntentProcessor(activity) { null }
|
||||||
|
processor.process(Intent(), navController, out)
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process when open extra is false`() {
|
||||||
|
val activity: HomeActivity = mockk()
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_BROWSER, false)
|
||||||
|
}
|
||||||
|
val processor = OpenBrowserIntentProcessor(activity) { null }
|
||||||
|
processor.process(intent, navController, out)
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process when open extra is true`() {
|
||||||
|
val activity: HomeActivity = mockk(relaxed = true)
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk(relaxed = true)
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_BROWSER, true)
|
||||||
|
}
|
||||||
|
val processor = OpenBrowserIntentProcessor(activity) { "session-id" }
|
||||||
|
processor.process(intent, navController, out)
|
||||||
|
|
||||||
|
verify { activity.openToBrowser(BrowserDirection.FromGlobal, "session-id") }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out.putExtra(HomeActivity.OPEN_TO_BROWSER, false) }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.Called
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.BrowserDirection
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.IntentReceiverActivity
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@ObsoleteCoroutinesApi
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class SpeechProcessingIntentProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process blank intents`() {
|
||||||
|
val activity: HomeActivity = mockk()
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
val processor = SpeechProcessingIntentProcessor(activity)
|
||||||
|
processor.process(Intent(), navController, out)
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process when open extra is false`() {
|
||||||
|
val activity: HomeActivity = mockk()
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false)
|
||||||
|
}
|
||||||
|
val processor = SpeechProcessingIntentProcessor(activity)
|
||||||
|
processor.process(intent, navController, out)
|
||||||
|
|
||||||
|
verify { activity wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process when open extra is true`() {
|
||||||
|
val activity: HomeActivity = mockk(relaxed = true)
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk(relaxed = true)
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true)
|
||||||
|
}
|
||||||
|
val processor = SpeechProcessingIntentProcessor(activity)
|
||||||
|
processor.process(intent, navController, out)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = "",
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromGlobal,
|
||||||
|
forceSearch = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out.putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `reads the speech processing extra`() {
|
||||||
|
val activity: HomeActivity = mockk(relaxed = true)
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_BROWSER_AND_LOAD, true)
|
||||||
|
putExtra(IntentReceiverActivity.SPEECH_PROCESSING, "hello world")
|
||||||
|
}
|
||||||
|
val processor = SpeechProcessingIntentProcessor(activity)
|
||||||
|
processor.process(intent, mockk(), mockk(relaxed = true))
|
||||||
|
|
||||||
|
verify {
|
||||||
|
activity.openToBrowserAndLoad(
|
||||||
|
searchTermOrURL = "hello world",
|
||||||
|
newTab = true,
|
||||||
|
from = BrowserDirection.FromGlobal,
|
||||||
|
forceSearch = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
package org.mozilla.fenix.home.intent
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import io.mockk.Called
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.HomeActivity
|
||||||
|
import org.mozilla.fenix.NavGraphDirections
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
import org.mozilla.fenix.components.metrics.MetricController
|
||||||
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@ObsoleteCoroutinesApi
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class StartSearchIntentProcessorTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process blank intents`() {
|
||||||
|
val metrics: MetricController = mockk()
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
StartSearchIntentProcessor(metrics).process(Intent(), navController, out)
|
||||||
|
|
||||||
|
verify { metrics wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `do not process when search extra is false`() {
|
||||||
|
val metrics: MetricController = mockk()
|
||||||
|
val navController: NavController = mockk()
|
||||||
|
val out: Intent = mockk()
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_SEARCH, false)
|
||||||
|
}
|
||||||
|
StartSearchIntentProcessor(metrics).process(intent, navController, out)
|
||||||
|
|
||||||
|
verify { metrics wasNot Called }
|
||||||
|
verify { navController wasNot Called }
|
||||||
|
verify { out wasNot Called }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `process search intents`() {
|
||||||
|
val metrics: MetricController = mockk(relaxed = true)
|
||||||
|
val navController: NavController = mockk(relaxed = true)
|
||||||
|
val out: Intent = mockk(relaxed = true)
|
||||||
|
val intent = Intent().apply {
|
||||||
|
putExtra(HomeActivity.OPEN_TO_SEARCH, true)
|
||||||
|
}
|
||||||
|
StartSearchIntentProcessor(metrics).process(intent, navController, out)
|
||||||
|
|
||||||
|
verify { metrics.track(Event.SearchWidgetNewTabPressed) }
|
||||||
|
verify {
|
||||||
|
navController.navigate(
|
||||||
|
NavGraphDirections.actionGlobalSearch(sessionId = null, showShortcutEnginePicker = true)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
verify { out.putExtra(HomeActivity.OPEN_TO_SEARCH, false) }
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue