diff --git a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt index 02daa064d1..4f6d774daa 100644 --- a/app/src/main/java/org/mozilla/fenix/HomeActivity.kt +++ b/app/src/main/java/org/mozilla/fenix/HomeActivity.kt @@ -6,8 +6,6 @@ package org.mozilla.fenix import android.content.Context import android.content.Intent -import android.net.Uri -import android.os.Build import android.os.Bundle import android.util.AttributeSet 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.SessionManager import mozilla.components.concept.engine.EngineView -import mozilla.components.lib.crash.Crash import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.ktx.kotlin.isUrl 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.getRootView 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.theme.DefaultThemeManager import org.mozilla.fenix.theme.ThemeManager @@ -61,6 +63,15 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback 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?) { super.onCreate(savedInstanceState) @@ -105,8 +116,10 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback */ final override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - handleCrashIfNecessary(intent) - handleOpenedFromExternalSourceIfNecessary(intent) + intent ?: return + + 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() } - handleOpenedFromExternalSourceIfNecessary(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) + externalSourceIntentProcessors.any { it.process(intent, navHost.navController, this.intent) } } protected open fun getIntentSessionId(intent: SafeIntent): String? = null @@ -264,11 +198,7 @@ open class HomeActivity : AppCompatActivity(), ShareFragment.TabsSharedCallback sessionObserver = subscribeToSessions() with(navHost.navController) { - if (currentDestination?.id == R.id.browserFragment || popBackStack( - R.id.browserFragment, - false - ) - ) return + if (currentDestination?.id == R.id.browserFragment || popBackStack(R.id.browserFragment, false)) return } @IdRes val fragmentId = if (from.fragmentId != 0) from.fragmentId else null diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt new file mode 100644 index 0000000000..dfc6feac44 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessor.kt @@ -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) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessor.kt new file mode 100644 index 0000000000..ba3e5aaa5f --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessor.kt @@ -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 + ) + } + } + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/HomeIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/HomeIntentProcessor.kt new file mode 100644 index 0000000000..ddc1e86f85 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/intent/HomeIntentProcessor.kt @@ -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 +} diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/OpenBrowserIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/OpenBrowserIntentProcessor.kt new file mode 100644 index 0000000000..d9d611d864 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/intent/OpenBrowserIntentProcessor.kt @@ -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 + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt new file mode 100644 index 0000000000..fdaba44e73 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessor.kt @@ -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 + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt new file mode 100644 index 0000000000..3cf31fe9d2 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessor.kt @@ -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 + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessorTest.kt new file mode 100644 index 0000000000..711901d076 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/intent/CrashReporterIntentProcessorTest.kt @@ -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()) + } + CrashReporterIntentProcessor().process(intent, navController, out) + + verify { navController.navigate(NavGraphDirections.actionGlobalCrashReporter(intent)) } + verify { out wasNot Called } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessorTest.kt new file mode 100644 index 0000000000..8427e838c3 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/intent/DeepLinkIntentProcessorTest.kt @@ -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()) +} diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/OpenBrowserIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/OpenBrowserIntentProcessorTest.kt new file mode 100644 index 0000000000..04b43a3d85 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/intent/OpenBrowserIntentProcessorTest.kt @@ -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) } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt new file mode 100644 index 0000000000..3ee8806659 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/intent/SpeechProcessingIntentProcessorTest.kt @@ -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 + ) + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt new file mode 100644 index 0000000000..9f20453c6f --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/home/intent/StartSearchIntentProcessorTest.kt @@ -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) } + } +}