diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index 34b5e2255..24bc20d17 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -98,6 +98,8 @@ import org.mozilla.fenix.home.sessioncontrol.SessionControlView import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder import org.mozilla.fenix.onboarding.FenixOnboarding import org.mozilla.fenix.settings.SupportUtils +import org.mozilla.fenix.settings.SupportUtils.MozillaPage.PRIVATE_NOTICE +import org.mozilla.fenix.settings.SupportUtils.SumoTopic.HELP import org.mozilla.fenix.settings.deletebrowsingdata.deleteAndQuit import org.mozilla.fenix.tabtray.TabTrayDialogFragment import org.mozilla.fenix.theme.ThemeManager @@ -230,7 +232,7 @@ class HomeFragment : Fragment() { openSettingsScreen = ::openSettingsScreen, openSearchScreen = ::navigateToSearch, openWhatsNewLink = { openCustomTab(SupportUtils.getWhatsNewUrl(activity)) }, - openPrivacyNotice = { openCustomTab(SupportUtils.getPrivacyNoticeUrl()) } + openPrivacyNotice = { openCustomTab(SupportUtils.getMozillaPageUrl(PRIVATE_NOTICE)) } ) ) updateLayout(view) @@ -696,10 +698,7 @@ class HomeFragment : Fragment() { invokePendingDeleteJobs() hideOnboardingIfNeeded() (activity as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = SupportUtils.getSumoURLForTopic( - context, - SupportUtils.SumoTopic.HELP - ), + searchTermOrURL = SupportUtils.getSumoURLForTopic(context, HELP), newTab = true, from = BrowserDirection.FromHome ) diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt index d1fa99949..c69515c53 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchController.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchController.kt @@ -5,7 +5,6 @@ package org.mozilla.fenix.search -import android.content.Context import android.content.Intent import androidx.navigation.NavController import kotlinx.coroutines.CoroutineScope @@ -28,6 +27,8 @@ import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.settings.SupportUtils +import org.mozilla.fenix.settings.SupportUtils.MozillaPage.MANIFESTO /** * An interface that handles the view manipulation of the Search, triggered by the Interactor @@ -45,8 +46,9 @@ interface SearchController { fun handleSearchShortcutsButtonClicked() } +@Suppress("TooManyFunctions") class DefaultSearchController( - private val context: Context, + private val activity: HomeActivity, private val store: SearchFragmentStore, private val navController: NavController, private val viewLifecycleScope: CoroutineScope, @@ -54,41 +56,46 @@ class DefaultSearchController( ) : SearchController { override fun handleUrlCommitted(url: String) { - if (url == "about:crashes") { - // The list of past crashes can be accessed via "settings > about", but desktop and - // fennec users may be used to navigating to "about:crashes". So we intercept this here - // and open the crash list activity instead. - context.startActivity(Intent(context, CrashListActivity::class.java)) - return + when (url) { + "about:crashes" -> { + // The list of past crashes can be accessed via "settings > about", but desktop and + // fennec users may be used to navigating to "about:crashes". So we intercept this here + // and open the crash list activity instead. + activity.startActivity(Intent(activity, CrashListActivity::class.java)) + } + "moz://a" -> openSearchOrUrl(SupportUtils.getMozillaPageUrl(MANIFESTO)) + else -> if (url.isNotBlank()) { + openSearchOrUrl(url) + } } + } - if (url.isNotBlank()) { - (context as HomeActivity).openToBrowserAndLoad( - searchTermOrURL = url, - newTab = store.state.session == null, - from = BrowserDirection.FromSearch, - engine = store.state.searchEngineSource.searchEngine - ) + private fun openSearchOrUrl(url: String) { + activity.openToBrowserAndLoad( + searchTermOrURL = url, + newTab = store.state.session == null, + from = BrowserDirection.FromSearch, + engine = store.state.searchEngineSource.searchEngine + ) - val event = if (url.isUrl()) { - Event.EnteredUrl(false) - } else { - val searchAccessPoint = when (store.state.searchAccessPoint) { - NONE -> ACTION - else -> store.state.searchAccessPoint - } - - searchAccessPoint?.let { sap -> - MetricsUtils.createSearchEvent( - store.state.searchEngineSource.searchEngine, - context, - sap - ) - } + val event = if (url.isUrl()) { + Event.EnteredUrl(false) + } else { + val searchAccessPoint = when (store.state.searchAccessPoint) { + NONE -> ACTION + else -> store.state.searchAccessPoint } - event?.let { context.metrics.track(it) } + searchAccessPoint?.let { sap -> + MetricsUtils.createSearchEvent( + store.state.searchEngineSource.searchEngine, + activity, + sap + ) + } } + + event?.let { activity.metrics.track(it) } } override fun handleEditingCancelled() { @@ -102,6 +109,7 @@ class DefaultSearchController( } override fun handleTextChanged(text: String) { + val settings = activity.settings() // Display the search shortcuts on each entry of the search fragment (see #5308) val textMatchesCurrentUrl = store.state.session?.url ?: "" == text val textMatchesCurrentSearch = store.state.session?.searchTerms ?: "" == text @@ -110,31 +118,31 @@ class DefaultSearchController( store.dispatch( SearchFragmentAction.ShowSearchShortcutEnginePicker( (textMatchesCurrentUrl || textMatchesCurrentSearch || text.isEmpty()) && - context.settings().shouldShowSearchShortcuts + settings.shouldShowSearchShortcuts ) ) store.dispatch( SearchFragmentAction.AllowSearchSuggestionsInPrivateModePrompt( text.isNotEmpty() && - (context as HomeActivity).browsingModeManager.mode.isPrivate && - !context.settings().shouldShowSearchSuggestionsInPrivate && - !context.settings().showSearchSuggestionsInPrivateOnboardingFinished + activity.browsingModeManager.mode.isPrivate && + !settings.shouldShowSearchSuggestionsInPrivate && + !settings.showSearchSuggestionsInPrivateOnboardingFinished ) ) } override fun handleUrlTapped(url: String) { - (context as HomeActivity).openToBrowserAndLoad( + activity.openToBrowserAndLoad( searchTermOrURL = url, newTab = store.state.session == null, from = BrowserDirection.FromSearch ) - context.metrics.track(Event.EnteredUrl(false)) + activity.metrics.track(Event.EnteredUrl(false)) } override fun handleSearchTermsTapped(searchTerms: String) { - (context as HomeActivity).openToBrowserAndLoad( + activity.openToBrowserAndLoad( searchTermOrURL = searchTerms, newTab = store.state.session == null, from = BrowserDirection.FromSearch, @@ -150,18 +158,18 @@ class DefaultSearchController( val event = searchAccessPoint?.let { sap -> MetricsUtils.createSearchEvent( store.state.searchEngineSource.searchEngine, - context, + activity, sap ) } - event?.let { context.metrics.track(it) } + event?.let { activity.metrics.track(it) } } override fun handleSearchShortcutEngineSelected(searchEngine: SearchEngine) { store.dispatch(SearchFragmentAction.SearchShortcutEngineSelected(searchEngine)) val isCustom = - CustomSearchEngineStore.isCustomSearchEngine(context, searchEngine.identifier) - context.metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom)) + CustomSearchEngineStore.isCustomSearchEngine(activity, searchEngine.identifier) + activity.metrics.track(Event.SearchShortcutSelected(searchEngine, isCustom)) } override fun handleSearchShortcutsButtonClicked() { @@ -175,14 +183,14 @@ class DefaultSearchController( } override fun handleExistingSessionSelected(session: Session) { - context.components.core.sessionManager.select(session) - (context as HomeActivity).openToBrowser( + activity.components.core.sessionManager.select(session) + activity.openToBrowser( from = BrowserDirection.FromSearch ) } override fun handleExistingSessionSelected(tabId: String) { - val session = context.components.core.sessionManager.findSessionById(tabId) + val session = activity.components.core.sessionManager.findSessionById(tabId) if (session != null) { handleExistingSessionSelected(session) } diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index 9e0005798..fa411e7ca 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -81,11 +81,10 @@ class SearchFragment : Fragment(), UserInteractionHandler { container: ViewGroup?, savedInstanceState: Bundle? ): View? { - val args = arguments?.let { navArgs().value } - val session = args?.sessionId + val activity = activity as HomeActivity + val args by navArgs() + val session = args.sessionId ?.let(requireComponents.core.sessionManager::findSessionById) - val pastedText = args?.pastedText - val searchAccessPoint = args?.searchAccessPoint val view = inflater.inflate(R.layout.fragment_search, container, false) val url = session?.url.orEmpty() @@ -93,7 +92,7 @@ class SearchFragment : Fragment(), UserInteractionHandler { requireComponents.search.provider.getDefaultEngine(requireContext()) ) - val isPrivate = (activity as HomeActivity).browsingModeManager.mode.isPrivate + val isPrivate = activity.browsingModeManager.mode.isPrivate requireComponents.analytics.metrics.track(Event.InteractWithSearchURLArea) @@ -110,14 +109,14 @@ class SearchFragment : Fragment(), UserInteractionHandler { showHistorySuggestions = requireContext().settings().shouldShowHistorySuggestions, showBookmarkSuggestions = requireContext().settings().shouldShowBookmarkSuggestions, session = session, - pastedText = pastedText, - searchAccessPoint = searchAccessPoint + pastedText = args.pastedText, + searchAccessPoint = args.searchAccessPoint ) ) } val searchController = DefaultSearchController( - context = activity as HomeActivity, + activity = activity, store = searchStore, navController = findNavController(), viewLifecycleScope = viewLifecycleOwner.lifecycleScope, diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index 1d21b0871..5ddffa766 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -263,7 +263,7 @@ class SettingsFragment : PreferenceFragmentCompat() { resources.getString(R.string.pref_key_privacy_link) -> { val intent = SupportUtils.createCustomTabIntent( requireContext(), - SupportUtils.getPrivacyNoticeUrl() + SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE) ) startActivity(intent) null diff --git a/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt b/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt index 6f2c35af1..372ac00de 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SupportUtils.kt @@ -43,6 +43,11 @@ object SupportUtils { SYNC_SETUP("how-set-firefox-sync-firefox-preview") } + enum class MozillaPage(internal val path: String) { + PRIVATE_NOTICE("privacy/firefox/"), + MANIFESTO("about/manifesto/") + } + /** * Gets a support page URL for the corresponding topic. */ @@ -69,8 +74,11 @@ object SupportUtils { return "https://support.mozilla.org/$langTag/kb/$escapedTopic" } - fun getPrivacyNoticeUrl(locale: Locale = Locale.getDefault()) = - "https://www.mozilla.org/${getLanguageTag(locale)}/privacy/firefox/" + fun getMozillaPageUrl(page: MozillaPage, locale: Locale = Locale.getDefault()): String { + val path = page.path + val langTag = getLanguageTag(locale) + return "https://www.mozilla.org/$langTag/$path" + } fun getWhatsNewUrl(context: Context) = if (Config.channel.isFennec) { getGenericSumoURLForTopic(SumoTopic.UPGRADE_FAQ) diff --git a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt index a9186f3b5..75183ae5e 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/about/AboutFragment.kt @@ -160,7 +160,7 @@ class AboutFragment : Fragment(), AboutPageListener { AboutPageItem.Item( AboutItem.ExternalLink( PRIVACY_NOTICE, - SupportUtils.getPrivacyNoticeUrl() + SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE) ), getString(R.string.about_privacy_notice) ), AboutPageItem.Item( diff --git a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt b/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt index 7d2308688..d5f9d159a 100644 --- a/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/DefaultSearchControllerTest.kt @@ -7,7 +7,6 @@ package org.mozilla.fenix.search import androidx.lifecycle.LifecycleCoroutineScope import androidx.navigation.NavController import androidx.navigation.NavDirections -import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -32,7 +31,9 @@ import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.ext.navigateSafe import org.mozilla.fenix.ext.searchEngineManager import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.search.DefaultSearchController.Companion.KEYBOARD_ANIMATION_DELAY +import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.whatsnew.clear @@ -40,7 +41,7 @@ import org.mozilla.fenix.whatsnew.clear @RunWith(FenixRobolectricTestRunner::class) class DefaultSearchControllerTest { - private val context: HomeActivity = mockk(relaxed = true) + private val activity: HomeActivity = mockk(relaxed = true) private val store: SearchFragmentStore = mockk(relaxed = true) private val navController: NavController = mockk(relaxed = true) private val defaultSearchEngine: SearchEngine? = mockk(relaxed = true) @@ -56,14 +57,14 @@ class DefaultSearchControllerTest { @Before fun setUp() { - every { context.searchEngineManager.defaultSearchEngine } returns defaultSearchEngine + every { activity.searchEngineManager.defaultSearchEngine } returns defaultSearchEngine every { store.state.session } returns session every { store.state.searchEngineSource.searchEngine } returns searchEngine - every { context.metrics } returns metrics - every { context.components.core.sessionManager } returns sessionManager + every { activity.metrics } returns metrics + every { activity.components.core.sessionManager } returns sessionManager controller = DefaultSearchController( - context = context, + activity = activity, store = store, navController = navController, viewLifecycleScope = lifecycleScope, @@ -80,7 +81,7 @@ class DefaultSearchControllerTest { controller.handleUrlCommitted(url) verify { - context.openToBrowserAndLoad( + activity.openToBrowserAndLoad( searchTermOrURL = url, newTab = session == null, from = BrowserDirection.FromSearch, @@ -90,10 +91,36 @@ class DefaultSearchControllerTest { verify { metrics.track(Event.EnteredUrl(false)) } } + @Test + fun handleCrashesUrlCommitted() { + val url = "about:crashes" + + controller.handleUrlCommitted(url) + + verify { activity.startActivity(any()) } + } + + @Test + fun handleMozillaUrlCommitted() { + val url = "moz://a" + + controller.handleUrlCommitted(url) + + verify { + activity.openToBrowserAndLoad( + searchTermOrURL = SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO), + newTab = session == null, + from = BrowserDirection.FromSearch, + engine = searchEngine + ) + } + verify { metrics.track(Event.EnteredUrl(false)) } + } + @Test fun handleEditingCancelled() = runBlockingTest { controller = DefaultSearchController( - context = context, + activity = activity, store = store, navController = navController, viewLifecycleScope = this, @@ -197,7 +224,7 @@ class DefaultSearchControllerTest { controller.handleUrlTapped(url) verify { - context.openToBrowserAndLoad( + activity.openToBrowserAndLoad( searchTermOrURL = url, newTab = session == null, from = BrowserDirection.FromSearch @@ -213,7 +240,7 @@ class DefaultSearchControllerTest { controller.handleSearchTermsTapped(searchTerms) verify { - context.openToBrowserAndLoad( + activity.openToBrowserAndLoad( searchTermOrURL = searchTerms, newTab = session == null, from = BrowserDirection.FromSearch, @@ -268,6 +295,6 @@ class DefaultSearchControllerTest { controller.handleExistingSessionSelected(session) verify { sessionManager.select(session) } - verify { context.openToBrowser(from = BrowserDirection.FromSearch) } + verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } } } diff --git a/app/src/test/java/org/mozilla/fenix/settings/SupportUtilsTest.kt b/app/src/test/java/org/mozilla/fenix/settings/SupportUtilsTest.kt index 9fa0a7bce..2d8346c7a 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/SupportUtilsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/SupportUtilsTest.kt @@ -44,14 +44,14 @@ class SupportUtilsTest { } @Test - fun getPrivacyNoticeUrl() { + fun getMozillaPageUrl() { assertEquals( - "https://www.mozilla.org/en-CA/privacy/firefox/", - SupportUtils.getPrivacyNoticeUrl(Locale("en", "CA")) + "https://www.mozilla.org/en-US/about/manifesto/", + SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.MANIFESTO, Locale("en", "US")) ) assertEquals( "https://www.mozilla.org/zh/privacy/firefox/", - SupportUtils.getPrivacyNoticeUrl(Locale("zh")) + SupportUtils.getMozillaPageUrl(SupportUtils.MozillaPage.PRIVATE_NOTICE, Locale("zh")) ) }