Copione merged onto master
commit
fb2f9ccd70
|
@ -276,7 +276,7 @@ onboarding:
|
|||
description:
|
||||
The tracking protection preference was chosen from the onboarding card.
|
||||
extra_keys:
|
||||
position:
|
||||
setting:
|
||||
description: |
|
||||
A string that indicates the Tracking Protection policy STANDARD
|
||||
or STRICT. Default: Toggle ON, STANDARD
|
||||
|
@ -497,6 +497,43 @@ metrics:
|
|||
notification_emails:
|
||||
- fenix-core@mozilla.com
|
||||
expires: "2020-09-01"
|
||||
recently_used_pwa_count:
|
||||
type: counter
|
||||
lifetime: application
|
||||
description: |
|
||||
A counter that indicates how many PWAs a user has recently used.
|
||||
Threshold for "recency" set in HomeActivity#PWA_RECENTLY_USED_THRESHOLD.
|
||||
Currently we are not told by the OS when a PWA is removed by the user,
|
||||
so we use the "recently used" heuristic to judge how many PWAs are still
|
||||
active, as a proxy for "installed". This value will only be set if the
|
||||
user has at least *one* recently used PWA. If they have 0, this metric
|
||||
will not be sent, resulting in a null value during analysis on the
|
||||
server-side. To disambiguate between a failed `recently_used_pwa_count`
|
||||
metric and 0 recent PWAs, please see `has_recent_pwas`.
|
||||
send_in_pings:
|
||||
- metrics
|
||||
bugs:
|
||||
- https://github.com/mozilla-mobile/fenix/issues/11909
|
||||
data_reviews:
|
||||
- https://github.com/mozilla-mobile/fenix/pull/11982#pullrequestreview-437963817
|
||||
notification_emails:
|
||||
- fenix-core@mozilla.com
|
||||
expires: "2020-12-01"
|
||||
has_recent_pwas:
|
||||
type: boolean
|
||||
lifetime: application
|
||||
description: |
|
||||
A boolean that indicates if the user has recently used PWAs.
|
||||
See recently_used_pwa_count for the actual count.
|
||||
send_in_pings:
|
||||
- metrics
|
||||
bugs:
|
||||
- https://github.com/mozilla-mobile/fenix/issues/11909
|
||||
data_reviews:
|
||||
- https://github.com/mozilla-mobile/fenix/pull/11982#pullrequestreview-437963817
|
||||
notification_emails:
|
||||
- fenix-core@mozilla.com
|
||||
expires: "2020-12-01"
|
||||
search_count:
|
||||
type: labeled_counter
|
||||
description: |
|
||||
|
|
|
@ -50,8 +50,8 @@ class HomeScreenTest {
|
|||
// Verify Top Sites
|
||||
verifyExistingTopSitesList()
|
||||
verifyExistingTopSitesTabs("Wikipedia")
|
||||
verifyExistingTopSitesTabs("YouTube")
|
||||
verifyExistingTopSitesTabs("Top Articles")
|
||||
verifyExistingTopSitesTabs("Google")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -148,7 +148,7 @@ class TopSitesTest {
|
|||
val defaultTopSites = arrayOf(
|
||||
"Top Articles",
|
||||
"Wikipedia",
|
||||
"YouTube"
|
||||
"Google"
|
||||
)
|
||||
|
||||
homeScreen { }.dismissOnboarding()
|
||||
|
|
|
@ -37,8 +37,10 @@ import mozilla.components.support.rusthttp.RustHttpConfig
|
|||
import mozilla.components.support.rustlog.RustLog
|
||||
import mozilla.components.support.utils.logElapsedTime
|
||||
import mozilla.components.support.webextensions.WebExtensionSupport
|
||||
import org.mozilla.fenix.StrictModeManager.enableStrictMode
|
||||
import org.mozilla.fenix.components.Components
|
||||
import org.mozilla.fenix.components.metrics.MetricServiceType
|
||||
import org.mozilla.fenix.ext.resetPoliciesAfter
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.perf.StartupTimeline
|
||||
import org.mozilla.fenix.push.PushFxaIntegration
|
||||
|
@ -47,8 +49,6 @@ import org.mozilla.fenix.session.PerformanceActivityLifecycleCallbacks
|
|||
import org.mozilla.fenix.session.VisibilityLifecycleCallback
|
||||
import org.mozilla.fenix.utils.BrowsersCache
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
import org.mozilla.fenix.StrictModeManager.enableStrictMode
|
||||
import org.mozilla.fenix.ext.resetPoliciesAfter
|
||||
|
||||
/**
|
||||
*The main application class for Fenix. Records data to measure initialization performance.
|
||||
|
@ -169,7 +169,7 @@ open class FenixApplication : LocaleAwareApplication() {
|
|||
|
||||
// Enable the service-experiments component to be initialized after visual completeness
|
||||
// for performance wins.
|
||||
if (settings().isExperimentationEnabled && Config.channel.isReleaseOrBeta) {
|
||||
if (settings().isExperimentationEnabled) {
|
||||
taskQueue.runIfReadyOrQueue {
|
||||
Experiments.initialize(
|
||||
applicationContext = applicationContext,
|
||||
|
|
|
@ -8,6 +8,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.os.StrictMode
|
||||
import android.text.format.DateUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
|
@ -27,6 +28,7 @@ import androidx.navigation.ui.NavigationUI
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import kotlinx.android.synthetic.main.activity_home.*
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.session.Session
|
||||
|
@ -50,6 +52,7 @@ import mozilla.components.support.locale.LocaleAwareAppCompatActivity
|
|||
import mozilla.components.support.utils.SafeIntent
|
||||
import mozilla.components.support.utils.toSafeIntent
|
||||
import mozilla.components.support.webextensions.WebExtensionPopupFeature
|
||||
import org.mozilla.fenix.GleanMetrics.Metrics
|
||||
import org.mozilla.fenix.browser.UriOpenedObserver
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
|
||||
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
|
||||
|
@ -188,6 +191,8 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
intent.removeExtra(START_IN_RECENTS_SCREEN)
|
||||
moveTaskToBack(true)
|
||||
}
|
||||
|
||||
captureSnapshotTelemetryMetrics()
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
|
@ -367,11 +372,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
* Everyone should call this instead of supportActionBar.
|
||||
*/
|
||||
fun getSupportActionBarAndInflateIfNecessary(): ActionBar {
|
||||
// Add ids to this that we don't want to have a toolbar back button
|
||||
if (!isToolbarInflated) {
|
||||
navigationToolbar = navigationToolbarStub.inflate() as Toolbar
|
||||
|
||||
setSupportActionBar(navigationToolbar)
|
||||
// Add ids to this that we don't want to have a toolbar back button
|
||||
setupNavigationToolbar()
|
||||
|
||||
isToolbarInflated = true
|
||||
|
@ -523,6 +528,23 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
this.visualCompletenessQueue = visualCompletenessQueue
|
||||
}
|
||||
|
||||
private fun captureSnapshotTelemetryMetrics() = CoroutineScope(Dispatchers.IO).launch {
|
||||
// PWA
|
||||
val recentlyUsedPwaCount = components.core.webAppShortcutManager.recentlyUsedWebAppsCount(
|
||||
activeThresholdMs = PWA_RECENTLY_USED_THRESHOLD
|
||||
)
|
||||
if (recentlyUsedPwaCount == 0) {
|
||||
Metrics.hasRecentPwas.set(false)
|
||||
} else {
|
||||
Metrics.hasRecentPwas.set(true)
|
||||
// This metric's lifecycle is set to 'application', meaning that it gets reset upon
|
||||
// application restart. Combined with the behaviour of the metric type itself (a growing counter),
|
||||
// it's important that this metric is only set once per application's lifetime.
|
||||
// Otherwise, we're going to over-count.
|
||||
Metrics.recentlyUsedPwaCount.add(recentlyUsedPwaCount)
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun isActivityColdStarted(startingIntent: Intent, activityIcicle: Bundle?): Boolean =
|
||||
// First time opening this activity in the task.
|
||||
|
@ -541,5 +563,9 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
|
|||
const val EXTRA_OPENED_FROM_NOTIFICATION = "notification_open"
|
||||
const val delay = 5000L
|
||||
const val START_IN_RECENTS_SCREEN = "start_in_recents_screen"
|
||||
|
||||
// PWA must have been used within last 30 days to be considered "recently used" for the
|
||||
// telemetry purposes.
|
||||
const val PWA_RECENTLY_USED_THRESHOLD = DateUtils.DAY_IN_MILLIS * 30L
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,6 +56,13 @@ class TopSiteStorage(private val context: Context) {
|
|||
private fun addDefaultTopSites() {
|
||||
val topSiteCandidates = mutableListOf<Pair<String, String>>()
|
||||
if (!context.settings().defaultTopSitesAdded) {
|
||||
topSiteCandidates.add(
|
||||
Pair(
|
||||
context.getString(R.string.default_top_site_google),
|
||||
SupportUtils.GOOGLE_URL
|
||||
)
|
||||
)
|
||||
|
||||
if (LocaleManager.getSelectedLocale(context).language == "en") {
|
||||
topSiteCandidates.add(
|
||||
Pair(
|
||||
|
@ -72,13 +79,6 @@ class TopSiteStorage(private val context: Context) {
|
|||
)
|
||||
)
|
||||
|
||||
topSiteCandidates.add(
|
||||
Pair(
|
||||
context.getString(R.string.default_top_site_youtube),
|
||||
SupportUtils.YOUTUBE_URL
|
||||
)
|
||||
)
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
topSiteCandidates.forEach { (title, url) ->
|
||||
addTopSite(title, url, isDefault = true)
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.mozilla.fenix.GleanMetrics.CrashReporter
|
|||
import org.mozilla.fenix.GleanMetrics.ErrorPage
|
||||
import org.mozilla.fenix.GleanMetrics.Events
|
||||
import org.mozilla.fenix.GleanMetrics.Logins
|
||||
import org.mozilla.fenix.GleanMetrics.Onboarding
|
||||
import org.mozilla.fenix.GleanMetrics.PerfAwesomebar
|
||||
import org.mozilla.fenix.GleanMetrics.SearchShortcuts
|
||||
import org.mozilla.fenix.GleanMetrics.Tip
|
||||
|
@ -188,22 +189,22 @@ sealed class Event {
|
|||
data class OnboardingToolbarPosition(val position: Position) : Event() {
|
||||
enum class Position { TOP, BOTTOM }
|
||||
|
||||
override val extras: Map<ToolbarSettings.changedPositionKeys, String>?
|
||||
get() = hashMapOf(ToolbarSettings.changedPositionKeys.position to position.name)
|
||||
override val extras: Map<Onboarding.prefToggledToolbarPositionKeys, String>?
|
||||
get() = hashMapOf(Onboarding.prefToggledToolbarPositionKeys.position to position.name)
|
||||
}
|
||||
|
||||
data class OnboardingTrackingProtection(val setting: Setting) : Event() {
|
||||
enum class Setting { STRICT, STANDARD }
|
||||
|
||||
override val extras: Map<TrackingProtection.etpSettingChangedKeys, String>?
|
||||
get() = hashMapOf(TrackingProtection.etpSettingChangedKeys.etpSetting to setting.name)
|
||||
override val extras: Map<Onboarding.prefToggledTrackingProtKeys, String>?
|
||||
get() = hashMapOf(Onboarding.prefToggledTrackingProtKeys.setting to setting.name)
|
||||
}
|
||||
|
||||
data class OnboardingThemePicker(val theme: Theme) : Event() {
|
||||
enum class Theme { LIGHT, DARK, FOLLOW_DEVICE }
|
||||
|
||||
override val extras: Map<AppTheme.darkThemeSelectedKeys, String>?
|
||||
get() = mapOf(AppTheme.darkThemeSelectedKeys.source to theme.name)
|
||||
override val extras: Map<Onboarding.prefToggledThemePickerKeys, String>?
|
||||
get() = mapOf(Onboarding.prefToggledThemePickerKeys.theme to theme.name)
|
||||
}
|
||||
|
||||
data class PreferenceToggled(
|
||||
|
|
|
@ -6,7 +6,6 @@ package org.mozilla.fenix.components.searchengine
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import mozilla.components.browser.icons.IconRequest
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.search.SearchEngineParser
|
||||
|
@ -126,6 +125,5 @@ object CustomSearchEngineStore {
|
|||
}
|
||||
|
||||
private const val PREF_KEY_CUSTOM_SEARCH_ENGINES = "pref_custom_search_engines"
|
||||
@VisibleForTesting
|
||||
const val PREF_FILE_SEARCH_ENGINES = "custom-search-engines"
|
||||
}
|
||||
|
|
|
@ -135,24 +135,28 @@ open class FenixSearchEngineProvider(
|
|||
return installedSearchEngines(context)
|
||||
}
|
||||
|
||||
fun installSearchEngine(context: Context, searchEngine: SearchEngine) = runBlocking {
|
||||
val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet()
|
||||
installedIdentifiers.add(searchEngine.identifier)
|
||||
prefs(context).edit().putStringSet(localeAwareInstalledEnginesKey(), installedIdentifiers).apply()
|
||||
fun installSearchEngine(context: Context, searchEngine: SearchEngine, isCustom: Boolean = false) = runBlocking {
|
||||
if (isCustom) {
|
||||
val searchUrl = searchEngine.getSearchTemplate()
|
||||
CustomSearchEngineStore.addSearchEngine(context, searchEngine.name, searchUrl)
|
||||
reload()
|
||||
} else {
|
||||
val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet()
|
||||
installedIdentifiers.add(searchEngine.identifier)
|
||||
prefs(context).edit()
|
||||
.putStringSet(localeAwareInstalledEnginesKey(), installedIdentifiers).apply()
|
||||
}
|
||||
}
|
||||
|
||||
fun uninstallSearchEngine(context: Context, searchEngine: SearchEngine) = runBlocking {
|
||||
val isCustom = CustomSearchEngineStore.isCustomSearchEngine(context, searchEngine.identifier)
|
||||
|
||||
fun uninstallSearchEngine(context: Context, searchEngine: SearchEngine, isCustom: Boolean = false) = runBlocking {
|
||||
if (isCustom) {
|
||||
CustomSearchEngineStore.removeSearchEngine(context, searchEngine.identifier)
|
||||
reload()
|
||||
} else {
|
||||
val installedIdentifiers = installedSearchEngineIdentifiers(context).toMutableSet()
|
||||
installedIdentifiers.remove(searchEngine.identifier)
|
||||
prefs(context).edit().putStringSet(localeAwareInstalledEnginesKey(), installedIdentifiers).apply()
|
||||
}
|
||||
|
||||
reload()
|
||||
}
|
||||
|
||||
fun reload() {
|
||||
|
@ -179,7 +183,7 @@ open class FenixSearchEngineProvider(
|
|||
}
|
||||
|
||||
private fun prefs(context: Context) = context.getSharedPreferences(
|
||||
PREF_FILE,
|
||||
PREF_FILE_SEARCH_ENGINES,
|
||||
Context.MODE_PRIVATE
|
||||
)
|
||||
|
||||
|
@ -225,7 +229,7 @@ open class FenixSearchEngineProvider(
|
|||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
companion object {
|
||||
val BUNDLED_SEARCH_ENGINES = listOf("reddit", "youtube")
|
||||
const val PREF_FILE = "fenix-search-engine-provider"
|
||||
const val PREF_FILE_SEARCH_ENGINES = "fenix-search-engine-provider"
|
||||
const val INSTALLED_ENGINES_KEY = "fenix-installed-search-engines"
|
||||
const val CURRENT_LOCALE_KEY = "fenix-current-locale"
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ class DefaultToolbarMenu(
|
|||
onAddonsManagerTapped = {
|
||||
onItemTapped.invoke(ToolbarMenu.Item.AddonsManager)
|
||||
},
|
||||
appendExtensionActionAtStart = !shouldReverseItems
|
||||
appendExtensionSubMenuAtStart = !shouldReverseItems
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.mozilla.fenix.R
|
|||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.sessionsOfType
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import org.mozilla.fenix.theme.ThemeManager
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
|
@ -81,16 +82,6 @@ class TabCounterToolbarButton(
|
|||
val primaryTextColor = ThemeManager.resolveAttribute(R.attr.primaryText, context)
|
||||
val metrics = context.components.analytics.metrics
|
||||
val menuItems = listOf(
|
||||
BrowserMenuImageText(
|
||||
label = context.getString(R.string.close_tab),
|
||||
imageResource = R.drawable.ic_close,
|
||||
iconTintColorResource = primaryTextColor,
|
||||
textColorResource = primaryTextColor
|
||||
) {
|
||||
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB))
|
||||
onItemTapped(TabCounterMenuItem.CloseTab)
|
||||
},
|
||||
BrowserMenuDivider(),
|
||||
BrowserMenuImageText(
|
||||
label = context.getString(R.string.browser_menu_new_tab),
|
||||
imageResource = R.drawable.ic_new,
|
||||
|
@ -108,9 +99,26 @@ class TabCounterToolbarButton(
|
|||
) {
|
||||
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.NEW_PRIVATE_TAB))
|
||||
onItemTapped(TabCounterMenuItem.NewTab(true))
|
||||
},
|
||||
BrowserMenuDivider(),
|
||||
BrowserMenuImageText(
|
||||
label = context.getString(R.string.close_tab),
|
||||
imageResource = R.drawable.ic_close,
|
||||
iconTintColorResource = primaryTextColor,
|
||||
textColorResource = primaryTextColor
|
||||
) {
|
||||
metrics.track(Event.TabCounterMenuItemTapped(Event.TabCounterMenuItemTapped.Item.CLOSE_TAB))
|
||||
onItemTapped(TabCounterMenuItem.CloseTab)
|
||||
}
|
||||
)
|
||||
return BrowserMenuBuilder(menuItems).build(context)
|
||||
|
||||
return BrowserMenuBuilder(
|
||||
if (context.settings().shouldUseBottomToolbar) {
|
||||
menuItems.reversed()
|
||||
} else {
|
||||
menuItems
|
||||
}
|
||||
).build(context)
|
||||
}
|
||||
|
||||
private val sessionManagerObserver = object : SessionManager.Observer {
|
||||
|
|
|
@ -248,11 +248,17 @@ class HomeFragment : Fragment() {
|
|||
* data in our store. The [View.consumeFrom] coroutine dispatch
|
||||
* doesn't get run right away which means that we won't draw on the first layout pass.
|
||||
*/
|
||||
fun updateSessionControlView(view: View) {
|
||||
sessionControlView?.update(homeFragmentStore.state)
|
||||
private fun updateSessionControlView(view: View) {
|
||||
if (browsingModeManager.mode == BrowsingMode.Private) {
|
||||
view.consumeFrom(homeFragmentStore, viewLifecycleOwner) {
|
||||
sessionControlView?.update(it)
|
||||
}
|
||||
} else {
|
||||
sessionControlView?.update(homeFragmentStore.state)
|
||||
|
||||
view.consumeFrom(homeFragmentStore, viewLifecycleOwner) {
|
||||
sessionControlView?.update(it)
|
||||
view.consumeFrom(homeFragmentStore, viewLifecycleOwner) {
|
||||
sessionControlView?.update(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import kotlinx.android.synthetic.main.component_bookmark.view.*
|
|||
import mozilla.appservices.places.BookmarkRoot
|
||||
import mozilla.components.concept.storage.BookmarkNode
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.NavGraphDirections
|
||||
import org.mozilla.fenix.library.LibraryPageView
|
||||
|
@ -140,17 +139,9 @@ class BookmarkView(
|
|||
bookmarkAdapter.updateData(state.tree, mode)
|
||||
when (mode) {
|
||||
is BookmarkFragmentState.Mode.Normal -> {
|
||||
if (tree != null) {
|
||||
if (BookmarkRoot.Mobile.id == tree?.guid) {
|
||||
(activity as HomeActivity).setupNavigationToolbar(R.id.bookmarkFragment)
|
||||
} else {
|
||||
(activity as HomeActivity).setupNavigationToolbar()
|
||||
}
|
||||
}
|
||||
setUiForNormalMode(state.tree)
|
||||
}
|
||||
is BookmarkFragmentState.Mode.Selecting -> {
|
||||
(activity as HomeActivity).setupNavigationToolbar()
|
||||
setUiForSelectingMode(
|
||||
context.getString(
|
||||
R.string.bookmarks_multi_select_title,
|
||||
|
|
|
@ -13,7 +13,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator
|
|||
import kotlinx.android.synthetic.main.component_history.*
|
||||
import kotlinx.android.synthetic.main.component_history.view.*
|
||||
import mozilla.components.support.base.feature.UserInteractionHandler
|
||||
import org.mozilla.fenix.HomeActivity
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.library.LibraryPageView
|
||||
import org.mozilla.fenix.library.SelectionInteractor
|
||||
|
@ -143,13 +142,11 @@ class HistoryView(
|
|||
|
||||
when (val mode = state.mode) {
|
||||
is HistoryFragmentState.Mode.Normal -> {
|
||||
(activity as HomeActivity).setupNavigationToolbar(R.id.historyFragment)
|
||||
setUiForNormalMode(
|
||||
context.getString(R.string.library_history)
|
||||
)
|
||||
}
|
||||
is HistoryFragmentState.Mode.Editing -> {
|
||||
(activity as HomeActivity).setupNavigationToolbar()
|
||||
setUiForSelectingMode(
|
||||
context.getString(R.string.history_multi_select_title, mode.selectedItems.size)
|
||||
)
|
||||
|
|
|
@ -46,6 +46,8 @@ import org.mozilla.fenix.HomeActivity
|
|||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.components.StoreProvider
|
||||
import org.mozilla.fenix.components.metrics.Event
|
||||
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
|
||||
import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider
|
||||
import org.mozilla.fenix.ext.components
|
||||
import org.mozilla.fenix.ext.getSpannable
|
||||
import org.mozilla.fenix.ext.hideToolbar
|
||||
|
@ -54,6 +56,7 @@ import org.mozilla.fenix.ext.settings
|
|||
import org.mozilla.fenix.search.awesomebar.AwesomeBarView
|
||||
import org.mozilla.fenix.search.toolbar.ToolbarView
|
||||
import org.mozilla.fenix.settings.SupportUtils
|
||||
import org.mozilla.fenix.settings.registerOnSharedPreferenceChangeListener
|
||||
import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_REQUEST_CODE
|
||||
|
||||
@Suppress("TooManyFunctions", "LargeClass")
|
||||
|
@ -128,6 +131,9 @@ class SearchFragment : Fragment(), UserInteractionHandler {
|
|||
|
||||
awesomeBarView = AwesomeBarView(view.scrollable_area, searchInteractor,
|
||||
view.findViewById(R.id.awesomeBar))
|
||||
setShortcutsChangedListener(CustomSearchEngineStore.PREF_FILE_SEARCH_ENGINES)
|
||||
setShortcutsChangedListener(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES)
|
||||
|
||||
view.scrollView.setOnScrollChangeListener {
|
||||
_: NestedScrollView, _: Int, _: Int, _: Int, _: Int ->
|
||||
view.hideKeyboard()
|
||||
|
@ -164,6 +170,15 @@ class SearchFragment : Fragment(), UserInteractionHandler {
|
|||
return (speechIntent.resolveActivity(requireContext().packageManager) != null)
|
||||
}
|
||||
|
||||
private fun setShortcutsChangedListener(preferenceFileName: String) {
|
||||
requireContext().getSharedPreferences(
|
||||
preferenceFileName,
|
||||
Context.MODE_PRIVATE
|
||||
).registerOnSharedPreferenceChangeListener(viewLifecycleOwner) { _, _ ->
|
||||
awesomeBarView.update(searchStore.state)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchVoiceSearch() {
|
||||
// Note if a user disables speech while the app is on the search fragment
|
||||
// the voice button will still be available and *will* cause a crash if tapped,
|
||||
|
|
|
@ -10,9 +10,9 @@ import androidx.appcompat.content.res.AppCompatResources
|
|||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import mozilla.components.feature.sitepermissions.SitePermissions
|
||||
import mozilla.components.support.ktx.android.content.res.resolveAttribute
|
||||
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
|
||||
import org.mozilla.fenix.ext.getPreferenceKey
|
||||
import org.mozilla.fenix.theme.ThemeManager
|
||||
|
||||
fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions {
|
||||
return update(featurePhone, get(featurePhone).toggle())
|
||||
|
@ -41,7 +41,7 @@ fun SitePermissions.update(field: PhoneFeature, value: SitePermissions.Status) =
|
|||
* as a result we have to apply it programmatically. More info about this issue https://github.com/mozilla-mobile/fenix/issues/1414
|
||||
*/
|
||||
fun RadioButton.setStartCheckedIndicator() {
|
||||
val attr = ThemeManager.resolveAttribute(android.R.attr.listChoiceIndicatorSingle, context)
|
||||
val attr = context.theme.resolveAttribute(android.R.attr.listChoiceIndicatorSingle)
|
||||
val buttonDrawable = AppCompatResources.getDrawable(context, attr)
|
||||
buttonDrawable?.apply {
|
||||
setBounds(0, 0, intrinsicWidth, intrinsicHeight)
|
||||
|
|
|
@ -42,28 +42,25 @@ enum class PhoneFeature(val androidPermissionsList: Array<String>) : Parcelable
|
|||
sitePermissions: SitePermissions? = null,
|
||||
settings: Settings? = null
|
||||
): String {
|
||||
@StringRes val stringRes =
|
||||
when (isAndroidPermissionGranted(context)) {
|
||||
false -> R.string.phone_feature_blocked_by_android
|
||||
else -> when (this) {
|
||||
AUTOPLAY_AUDIBLE -> {
|
||||
when (settings?.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) ?: AUTOPLAY_BLOCK_ALL) {
|
||||
AUTOPLAY_ALLOW_ALL -> R.string.preference_option_autoplay_allowed2
|
||||
AUTOPLAY_ALLOW_ON_WIFI -> R.string.preference_option_autoplay_allowed_wifi_only2
|
||||
AUTOPLAY_BLOCK_AUDIBLE -> R.string.preference_option_autoplay_block_audio2
|
||||
AUTOPLAY_BLOCK_ALL -> R.string.preference_option_autoplay_blocked3
|
||||
else -> R.string.preference_option_autoplay_blocked3
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when (getStatus(sitePermissions, settings)) {
|
||||
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
|
||||
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
|
||||
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
|
||||
}
|
||||
@StringRes val stringRes = if (isAndroidPermissionGranted(context)) {
|
||||
when (this) {
|
||||
AUTOPLAY_AUDIBLE ->
|
||||
when (settings?.getAutoplayUserSetting(default = AUTOPLAY_BLOCK_ALL) ?: AUTOPLAY_BLOCK_ALL) {
|
||||
AUTOPLAY_ALLOW_ALL -> R.string.preference_option_autoplay_allowed2
|
||||
AUTOPLAY_ALLOW_ON_WIFI -> R.string.preference_option_autoplay_allowed_wifi_only2
|
||||
AUTOPLAY_BLOCK_AUDIBLE -> R.string.preference_option_autoplay_block_audio2
|
||||
AUTOPLAY_BLOCK_ALL -> R.string.preference_option_autoplay_blocked3
|
||||
else -> R.string.preference_option_autoplay_blocked3
|
||||
}
|
||||
else -> when (getStatus(sitePermissions, settings)) {
|
||||
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
|
||||
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
|
||||
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
|
||||
}
|
||||
}
|
||||
} else {
|
||||
R.string.phone_feature_blocked_by_android
|
||||
}
|
||||
return context.getString(stringRes)
|
||||
}
|
||||
|
||||
|
@ -109,7 +106,7 @@ enum class PhoneFeature(val androidPermissionsList: Array<String>) : Parcelable
|
|||
fun getAction(settings: Settings): SitePermissionsRules.Action =
|
||||
settings.getSitePermissionsPhoneFeatureAction(this, getDefault())
|
||||
|
||||
fun getDefault(): SitePermissionsRules.Action {
|
||||
private fun getDefault(): SitePermissionsRules.Action {
|
||||
return when (this) {
|
||||
AUTOPLAY_AUDIBLE -> SitePermissionsRules.Action.BLOCKED
|
||||
AUTOPLAY_INAUDIBLE -> SitePermissionsRules.Action.ALLOWED
|
||||
|
|
|
@ -26,7 +26,7 @@ object SupportUtils {
|
|||
const val FENIX_PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=${BuildConfig.APPLICATION_ID}"
|
||||
const val FIREFOX_BETA_PLAY_STORE_URL = "market://details?id=org.mozilla.firefox_beta"
|
||||
const val FIREFOX_NIGHTLY_PLAY_STORE_URL = "market://details?id=org.mozilla.fennec_aurora"
|
||||
const val YOUTUBE_URL = "https://www.youtube.com/"
|
||||
const val GOOGLE_URL = "https://www.google.com/"
|
||||
|
||||
enum class SumoTopic(internal val topicStr: String) {
|
||||
FENIX_MOVING("sync-delist"),
|
||||
|
|
|
@ -18,7 +18,10 @@ import androidx.core.view.isVisible
|
|||
import androidx.navigation.Navigation
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import kotlinx.android.synthetic.main.search_engine_radio_button.view.*
|
||||
import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_icon
|
||||
import kotlinx.android.synthetic.main.search_engine_radio_button.view.engine_text
|
||||
import kotlinx.android.synthetic.main.search_engine_radio_button.view.overflow_menu
|
||||
import kotlinx.android.synthetic.main.search_engine_radio_button.view.radio_button
|
||||
import kotlinx.coroutines.MainScope
|
||||
import mozilla.components.browser.search.SearchEngine
|
||||
import mozilla.components.browser.search.provider.SearchEngineList
|
||||
|
@ -167,7 +170,7 @@ abstract class SearchEngineListPreference @JvmOverloads constructor(
|
|||
val initialEngineList = searchEngineList.copy()
|
||||
val initialDefaultEngine = searchEngineList.default
|
||||
|
||||
context.components.search.provider.uninstallSearchEngine(context, engine)
|
||||
context.components.search.provider.uninstallSearchEngine(context, engine, isCustomSearchEngine)
|
||||
|
||||
MainScope().allowUndo(
|
||||
view = context.getRootView()!!,
|
||||
|
@ -175,7 +178,7 @@ abstract class SearchEngineListPreference @JvmOverloads constructor(
|
|||
.getString(R.string.search_delete_search_engine_success_message, engine.name),
|
||||
undoActionTitle = context.getString(R.string.snackbar_deleted_undo),
|
||||
onCancel = {
|
||||
context.components.search.provider.installSearchEngine(context, engine)
|
||||
context.components.search.provider.installSearchEngine(context, engine, isCustomSearchEngine)
|
||||
|
||||
searchEngineList = initialEngineList.copy(
|
||||
default = initialDefaultEngine
|
||||
|
|
|
@ -20,6 +20,7 @@ import mozilla.components.concept.tabstray.TabsTray
|
|||
import mozilla.components.feature.media.ext.pauseIfPlaying
|
||||
import mozilla.components.feature.media.ext.playIfPaused
|
||||
import mozilla.components.support.base.observer.Observable
|
||||
import mozilla.components.support.images.ImageRequest
|
||||
import mozilla.components.support.images.loader.ImageLoader
|
||||
import mozilla.components.support.ktx.kotlin.tryGetHostFromUrl
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -71,7 +72,7 @@ class TabTrayViewHolder(
|
|||
if (tab.thumbnail != null) {
|
||||
thumbnailView.setImageBitmap(tab.thumbnail)
|
||||
} else {
|
||||
imageLoader.loadIntoView(thumbnailView, tab.id)
|
||||
imageLoader.loadIntoView(thumbnailView, ImageRequest(tab.id))
|
||||
}
|
||||
|
||||
// Media state
|
||||
|
|
|
@ -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.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import org.mozilla.fenix.R
|
||||
|
||||
/**
|
||||
* An [AppCompatTextView] that announces as link in screen readers for a11y purposes
|
||||
*/
|
||||
class LinkTextView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : AppCompatTextView(context, attrs, defStyleAttr) {
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(info: AccessibilityNodeInfo?) {
|
||||
super.onInitializeAccessibilityNodeInfo(info)
|
||||
val extras = info?.extras
|
||||
extras?.putCharSequence(
|
||||
"AccessibilityNodeInfo.roleDescription",
|
||||
context.resources.getString(R.string.link_text_view_type_announcement)
|
||||
)
|
||||
// disable long click announcement, as there is no action to be performed on long click
|
||||
info?.isLongClickable = false
|
||||
info?.removeAction(ACTION_LONG_CLICK)
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@
|
|||
android:visibility="visible"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
<org.mozilla.fenix.utils.LinkTextView
|
||||
android:id="@+id/exceptions_learn_more"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -37,7 +37,7 @@
|
|||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"/>
|
||||
|
||||
<TextView
|
||||
<org.mozilla.fenix.utils.LinkTextView
|
||||
android:id="@+id/saved_passwords_empty_learn_more"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
android:labelFor="@+id/edit_search_string"
|
||||
android:textColor="@android:color/tertiary_text_dark" />
|
||||
|
||||
<TextView
|
||||
<org.mozilla.fenix.utils.LinkTextView
|
||||
android:id="@+id/custom_search_engines_learn_more"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -66,7 +66,6 @@
|
|||
android:minHeight="@dimen/accessibility_min_height"
|
||||
android:textColor="?accent"
|
||||
android:visibility="visible"
|
||||
android:contentDescription="@string/search_add_custom_engine_learn_more_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/exceptions_empty_message" />
|
||||
|
||||
<ProgressBar
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/header_text"
|
||||
tools:text="@string/onboarding_whats_new_description" />
|
||||
|
||||
<TextView
|
||||
<org.mozilla.fenix.utils.LinkTextView
|
||||
android:id="@+id/get_answers"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
android:textSize="14sp"
|
||||
tools:text="@string/private_browsing_placeholder_description_2" />
|
||||
|
||||
<TextView
|
||||
<org.mozilla.fenix.utils.LinkTextView
|
||||
android:id="@+id/private_session_common_myths"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
tools:text="@string/search_suggestions_onboarding_text"
|
||||
tools:textAppearance="?attr/textAppearanceListItemSmall"/>
|
||||
|
||||
<TextView
|
||||
<org.mozilla.fenix.utils.LinkTextView
|
||||
android:id="@+id/learn_more"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
@ -362,6 +362,9 @@
|
|||
<!-- Preference for removing FxA account -->
|
||||
<string name="preferences_sync_remove_account">खाता मिटायें</string>
|
||||
|
||||
<!-- Pairing Feature strings -->
|
||||
<!-- Instructions on how to access pairing -->
|
||||
<string name="pair_instructions_2"><![CDATA[<b>firefox.com/pair</b> पर दिखाए गए QR कोड को स्कैन करें]]></string>
|
||||
<!-- Button to open camera for pairing -->
|
||||
<string name="pair_open_camera">कैमरा खोले</string>
|
||||
<!-- Button to cancel pairing -->
|
||||
|
@ -825,8 +828,6 @@
|
|||
<string name="preferences_delete_browsing_data_site_permissions">साइट की अनुमति</string>
|
||||
<!-- Text for the button to delete browsing data -->
|
||||
<string name="preferences_delete_browsing_data_button">ब्राउज़िंग डेटा मिटाएं</string>
|
||||
<!-- Category for history items to delete on quit in delete browsing data on quit -->
|
||||
<string name="preferences_delete_browsing_data_on_quit_browsing_history">ब्राउज़िंग इतिहास</string>
|
||||
|
||||
<!-- Action item in menu for the Delete browsing data on quit feature -->
|
||||
<string name="delete_browsing_data_on_quit_action">निकास</string>
|
||||
|
|
|
@ -313,7 +313,7 @@
|
|||
<!-- Text shown when user enters empty device name -->
|
||||
<string name="empty_device_name_error">Inserire un nome per il dispositivo.</string>
|
||||
<!-- Label indicating that sync is in progress -->
|
||||
<string name="sync_syncing_in_progress">Sincronizzazione…</string>
|
||||
<string name="sync_syncing_in_progress">Sincronizzazione in corso…</string>
|
||||
<!-- Label summary indicating that sync failed. The first parameter is the date stamp showing last time it succeeded -->
|
||||
<string name="sync_failed_summary">Sincronizzazione non riuscita. Ultima sincronizzazione: %s</string>
|
||||
<!-- Label summary showing never synced -->
|
||||
|
@ -922,8 +922,6 @@
|
|||
<string name="preference_summary_delete_browsing_data_on_quit">Elimina automaticamente i dati di navigazione quando viene selezionato “Esci” nel menu principale</string>
|
||||
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
|
||||
<string name="preference_summary_delete_browsing_data_on_quit_2">Elimina automaticamente i dati di navigazione quando viene selezionato “Esci” nel menu principale</string>
|
||||
<!-- Category for history items to delete on quit in delete browsing data on quit -->
|
||||
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Cronologia di navigazione</string>
|
||||
<!-- Action item in menu for the Delete browsing data on quit feature -->
|
||||
<string name="delete_browsing_data_on_quit_action">Esci</string>
|
||||
|
||||
|
|
|
@ -367,7 +367,7 @@
|
|||
<string name="preference_experiments">Experimentos</string>
|
||||
|
||||
<!-- Summary for experiments preferences -->
|
||||
<string name="preference_experiments_summary">Permite que o Mozilla instale e colete dados para recursos experimentais</string>
|
||||
<string name="preference_experiments_summary">Permitir que a Mozilla instale e colete dados para recursos experimentais</string>
|
||||
<!-- Preference switch for crash reporter -->
|
||||
<string name="preferences_crash_reporter">Relator de travamentos</string>
|
||||
<!-- Preference switch for Mozilla location service -->
|
||||
|
@ -887,8 +887,6 @@
|
|||
<string name="preference_summary_delete_browsing_data_on_quit">Excluir dados de navegação automaticamente ao selecionar "Sair" no menu principal.</string>
|
||||
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
|
||||
<string name="preference_summary_delete_browsing_data_on_quit_2">Excluir automaticamente os dados de navegação quando você selecionar \"Sair\" no menu principal.</string>
|
||||
<!-- Category for history items to delete on quit in delete browsing data on quit -->
|
||||
<string name="preferences_delete_browsing_data_on_quit_browsing_history">Histórico de navegação</string>
|
||||
<!-- Action item in menu for the Delete browsing data on quit feature -->
|
||||
<string name="delete_browsing_data_on_quit_action">Sair</string>
|
||||
|
||||
|
|
|
@ -874,8 +874,6 @@
|
|||
<string name="preference_summary_delete_browsing_data_on_quit">ลบข้อมูลการเรียกดูโดยอัตโนมัติเมื่อคุณเลือก "ออก" จากเมนูหลัก</string>
|
||||
<!-- Summary for the Delete browsing data on quit preference. "Quit" translation should match delete_browsing_data_on_quit_action translation. -->
|
||||
<string name="preference_summary_delete_browsing_data_on_quit_2">ลบข้อมูลการเรียกดูโดยอัตโนมัติเมื่อคุณเลือก \"ออก\" จากเมนูหลัก</string>
|
||||
<!-- Category for history items to delete on quit in delete browsing data on quit -->
|
||||
<string name="preferences_delete_browsing_data_on_quit_browsing_history">ประวัติการเรียกดู</string>
|
||||
<!-- Action item in menu for the Delete browsing data on quit feature -->
|
||||
<string name="delete_browsing_data_on_quit_action">ออก</string>
|
||||
|
||||
|
@ -900,6 +898,9 @@
|
|||
<string name="tip_firefox_preview_moved_description">Firefox Nightly อัปเดตทุกคืนและมีลูกเล่นใหม่ ๆ ทุกคืน
|
||||
อย่างไรก็ตามความเสถียรอาจลดลง ดาวน์โหลดเบราว์เซอร์เบต้าของเราเพื่อประสบการณ์ที่เสถียรยิ่งขึ้น</string>
|
||||
|
||||
<!-- text for firefox preview moving tip button. "Firefox for Android Beta" is intentionally hardcoded -->
|
||||
<string name="tip_firefox_preview_moved_button_2">รับ Firefox Beta สำหรับ Android</string>
|
||||
|
||||
<!-- text for firefox preview moving tip header. "Firefox Nightly" is intentionally hardcoded -->
|
||||
<string name="tip_firefox_preview_moved_header_preview_installed">Firefox Nightly ย้ายไปแล้ว</string>
|
||||
<!-- text for firefox preview moving tip description -->
|
||||
|
@ -951,6 +952,8 @@
|
|||
<string name="onboarding_firefox_account_sync_is_on">Sync เปิดอยู่</string>
|
||||
<!-- text to display in the snackbar if automatic sign-in fails. user may try again -->
|
||||
<string name="onboarding_firefox_account_automatic_signin_failed">ไม่สามารถเข้าสู่ระบบ</string>
|
||||
<!-- text for the tracking protection onboarding card header -->
|
||||
<string name="onboarding_tracking_protection_header_2">ความเป็นส่วนตัวอัตโนมัติ</string>
|
||||
<!-- text for tracking protection radio button option for strict level of blocking -->
|
||||
<string name="onboarding_tracking_protection_strict_button">เข้มงวด (แนะนำ)</string>
|
||||
<!-- text for tracking protection radio button option for strict level of blocking -->
|
||||
|
@ -1379,4 +1382,10 @@
|
|||
<!-- Text displayed to ask user to connect another device as no devices found with account -->
|
||||
<string name="synced_tabs_connect_another_device">เชื่อมต่ออุปกรณ์อื่น</string>
|
||||
|
||||
</resources>
|
||||
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
|
||||
<string name="synced_tabs_sign_in_button">ลงชื่อเข้าใช้เพื่อซิงค์</string>
|
||||
|
||||
<!-- Confirmation dialog button text when top sites limit is reached. -->
|
||||
<string name="top_sites_max_limit_confirmation_button">ตกลง เข้าใจแล้ว</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -1288,6 +1288,8 @@
|
|||
<string name="search_add_custom_engine_error_existing_name">“%s” نام والا تلاش انجن پہلے سے موجود ہے۔</string>
|
||||
<!-- Text shown when a user leaves the search string field empty -->
|
||||
<string name="search_add_custom_engine_error_empty_search_string">ایک تلاشی سٹرنگ داخل کریں</string>
|
||||
<!-- Text shown when a user leaves out the required template string -->
|
||||
<string name="search_add_custom_engine_error_missing_template">چیک کریں کہ سرچ سٹرنگ مثال کی شکل سے میل کھاتا ہے</string>
|
||||
<!-- Text shown when we aren't able to validate the custom search query. The first parameter is the url of the custom search engine -->
|
||||
<string name="search_add_custom_engine_error_cannot_reach">”%s“ سے رابطہ کرنے میں خرابی</string>
|
||||
<!-- Text shown when a user creates a new search engine -->
|
||||
|
@ -1381,6 +1383,8 @@
|
|||
<string name="synced_tabs_reauth">برائے مہربانی دوبارہ توثیق کریں۔</string>
|
||||
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
|
||||
<string name="synced_tabs_enable_tab_syncing">براہ کرم ٹیب sync کو فعال کریں۔</string>
|
||||
<!-- Text displayed in the synced tabs screen when a user is not signed in to Firefox Sync describing Synced Tabs -->
|
||||
<string name="synced_tabs_sign_in_message">دیگر آلات سے ٹیبیں کی فہرست دیکھیں۔</string>
|
||||
<!-- Text displayed on a button in the synced tabs screen to link users to sign in when a user is not signed in to Firefox Sync -->
|
||||
<string name="synced_tabs_sign_in_button">sync کے لئے سائن ان کریں</string>
|
||||
|
||||
|
|
|
@ -23,8 +23,8 @@
|
|||
<string name="pocket_pinned_top_articles" translatable="false">Top Articles</string>
|
||||
<!-- Default title for pinned Wikipedia top site that links to Wikipedia home page -->
|
||||
<string name="default_top_site_wikipedia" translatable="false">Wikipedia</string>
|
||||
<!-- Default title for pinned YouTube top site that links to Youtube home page -->
|
||||
<string name="default_top_site_youtube" translatable="false">YouTube</string>
|
||||
<!-- Default title for pinned Google top site that links to Google home page -->
|
||||
<string name="default_top_site_google" translatable="false">Google</string>
|
||||
<!-- Android Components abbreviation used in AboutFragment -->
|
||||
<string name="components_abbreviation" translatable="false">AC</string>
|
||||
<!-- Application Services abbreviation used in AboutFragment -->
|
||||
|
|
|
@ -1418,5 +1418,6 @@
|
|||
<string name="top_sites_max_limit_content">To add a new top site, remove one. Long press the site and select remove.</string>
|
||||
<!-- Confirmation dialog button text when top sites limit is reached. -->
|
||||
<string name="top_sites_max_limit_confirmation_button">OK, Got It</string>
|
||||
|
||||
<!-- Content description (not visible, for screen readers etc.) used to announce [LinkTextView]. -->
|
||||
<string name="link_text_view_type_announcement">link</string>
|
||||
</resources>
|
||||
|
|
|
@ -3,6 +3,11 @@ package org.mozilla.fenix.components.searchengine
|
|||
import android.content.Context
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.Runs
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.just
|
||||
import io.mockk.mockkObject
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
@ -27,6 +32,12 @@ class FenixSearchEngineProviderTest {
|
|||
@Before
|
||||
fun before() {
|
||||
fenixSearchEngineProvider = FakeFenixSearchEngineProvider(testContext)
|
||||
mockkObject(CustomSearchEngineStore)
|
||||
fenixSearchEngineProvider.let {
|
||||
every { CustomSearchEngineStore.loadCustomSearchEngines(testContext) } returns listOf(
|
||||
(it as FakeFenixSearchEngineProvider).mockSearchEngine("my custom site", "my custom site")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -37,6 +48,28 @@ class FenixSearchEngineProviderTest {
|
|||
- the above after adding/removing
|
||||
*/
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@Test
|
||||
fun `add custom engine`() = runBlockingTest {
|
||||
val engineName = "Ecosia"
|
||||
val engineQuery = "www.ecosia.com/%s"
|
||||
val searchEngine: SearchEngine = mockk(relaxed = true)
|
||||
every { searchEngine.getSearchTemplate() } returns engineQuery
|
||||
every { searchEngine.name } returns engineName
|
||||
mockkObject(CustomSearchEngineStore)
|
||||
coEvery {
|
||||
CustomSearchEngineStore.addSearchEngine(
|
||||
testContext,
|
||||
engineName,
|
||||
engineQuery
|
||||
)
|
||||
} just Runs
|
||||
|
||||
fenixSearchEngineProvider.installSearchEngine(testContext, searchEngine, true)
|
||||
|
||||
coVerify { CustomSearchEngineStore.addSearchEngine(testContext, engineName, engineQuery) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN sharedprefs does not contain installed engines WHEN installedSearchEngineIdentifiers THEN defaultEngines + customEngines ids are returned`() = runBlockingTest {
|
||||
val expectedDefaults = fenixSearchEngineProvider.baseSearchEngines.toIdSet()
|
||||
|
@ -49,7 +82,7 @@ class FenixSearchEngineProviderTest {
|
|||
|
||||
@Test
|
||||
fun `GIVEN sharedprefs contains installed engines WHEN installedSearchEngineIdentifiers THEN defaultEngines + customEngines ids are returned`() = runBlockingTest {
|
||||
val sp = testContext.getSharedPreferences(FenixSearchEngineProvider.PREF_FILE, Context.MODE_PRIVATE)
|
||||
val sp = testContext.getSharedPreferences(FenixSearchEngineProvider.PREF_FILE_SEARCH_ENGINES, Context.MODE_PRIVATE)
|
||||
sp.edit().putStringSet(fenixSearchEngineProvider.localeAwareInstalledEnginesKey(), persistedInstalledEngines).apply()
|
||||
|
||||
val expectedStored = persistedInstalledEngines
|
||||
|
@ -96,21 +129,17 @@ class FakeFenixSearchEngineProvider(context: Context) : FenixSearchEngineProvide
|
|||
)
|
||||
)
|
||||
|
||||
override var customSearchEngines: Deferred<SearchEngineList>
|
||||
get() {
|
||||
return CompletableDeferred(
|
||||
SearchEngineList(
|
||||
listOf(
|
||||
mockSearchEngine("my custom site", "my custom site")
|
||||
), default = null
|
||||
)
|
||||
)
|
||||
}
|
||||
set(_) = throw NotImplementedError("Setting not currently supported on this fake")
|
||||
override var customSearchEngines: Deferred<SearchEngineList> = CompletableDeferred(
|
||||
SearchEngineList(
|
||||
listOf(
|
||||
mockSearchEngine("my custom site", "my custom site")
|
||||
), default = null
|
||||
)
|
||||
)
|
||||
|
||||
override fun updateBaseSearchEngines() { }
|
||||
|
||||
private fun mockSearchEngine(
|
||||
fun mockSearchEngine(
|
||||
id: String,
|
||||
n: String = id
|
||||
): SearchEngine {
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/* 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.settings
|
||||
|
||||
import android.graphics.Rect
|
||||
import android.widget.RadioButton
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import io.mockk.Called
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class ExtensionsTest {
|
||||
|
||||
@MockK(relaxUnitFun = true) private lateinit var radioButton: RadioButton
|
||||
@MockK private lateinit var fragment: PreferenceFragmentCompat
|
||||
private lateinit var preference: Preference
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
preference = Preference(testContext)
|
||||
|
||||
every { radioButton.context } returns testContext
|
||||
every {
|
||||
fragment.getString(R.string.pref_key_accessibility_force_enable_zoom)
|
||||
} returns "pref_key_accessibility_force_enable_zoom"
|
||||
every {
|
||||
fragment.getString(R.string.pref_key_accessibility_auto_size)
|
||||
} returns "pref_key_accessibility_auto_size"
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test radiobutton setStartCheckedIndicator`() {
|
||||
radioButton.setStartCheckedIndicator()
|
||||
|
||||
verify { radioButton.putCompoundDrawablesRelative(start = withArg {
|
||||
assertEquals(Rect(0, 0, it.intrinsicWidth, it.intrinsicHeight), it.bounds)
|
||||
}) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `set change listener with typed argument`() {
|
||||
val callback = mockk<(Preference, String) -> Unit>(relaxed = true)
|
||||
preference.setOnPreferenceChangeListener<String> { pref, value ->
|
||||
callback(pref, value)
|
||||
true
|
||||
}
|
||||
|
||||
assertFalse(preference.callChangeListener(10))
|
||||
verify { callback wasNot Called }
|
||||
|
||||
assertTrue(preference.callChangeListener("Hello"))
|
||||
verify { callback(preference, "Hello") }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `requirePreference returns corresponding preference`() {
|
||||
val switchPreference = mockk<SwitchPreference>()
|
||||
every {
|
||||
fragment.findPreference<SwitchPreference>("pref_key_accessibility_auto_size")
|
||||
} returns switchPreference
|
||||
|
||||
assertEquals(
|
||||
switchPreference,
|
||||
fragment.requirePreference<SwitchPreference>(R.string.pref_key_accessibility_auto_size)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `requirePreference throws if null preference is returned`() {
|
||||
every {
|
||||
fragment.findPreference<SwitchPreference>("pref_key_accessibility_force_enable_zoom")
|
||||
} returns null
|
||||
|
||||
var exception: IllegalArgumentException? = null
|
||||
try {
|
||||
fragment.requirePreference<SwitchPreference>(R.string.pref_key_accessibility_force_enable_zoom)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
exception = e
|
||||
}
|
||||
|
||||
assertNotNull(exception)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/* 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.settings
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.LifecycleRegistry
|
||||
import io.mockk.Called
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class OnSharedPreferenceChangeListenerTest {
|
||||
|
||||
private lateinit var sharedPrefs: SharedPreferences
|
||||
private lateinit var listener: (SharedPreferences, String) -> Unit
|
||||
private lateinit var owner: LifecycleOwner
|
||||
private lateinit var lifecycleRegistry: LifecycleRegistry
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
sharedPrefs = mockk(relaxUnitFun = true)
|
||||
listener = mockk(relaxed = true)
|
||||
owner = LifecycleOwner { lifecycleRegistry }
|
||||
lifecycleRegistry = LifecycleRegistry(owner)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test listener is registered based on lifecycle`() {
|
||||
sharedPrefs.registerOnSharedPreferenceChangeListener(owner, listener)
|
||||
verify { sharedPrefs wasNot Called }
|
||||
|
||||
lifecycleRegistry.currentState = Lifecycle.State.CREATED
|
||||
verify { sharedPrefs.registerOnSharedPreferenceChangeListener(any()) }
|
||||
|
||||
lifecycleRegistry.currentState = Lifecycle.State.DESTROYED
|
||||
verify { sharedPrefs.unregisterOnSharedPreferenceChangeListener(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `listener should call lambda`() {
|
||||
val wrapper = OnSharedPreferenceChangeListener(mockk(), listener)
|
||||
wrapper.onSharedPreferenceChanged(sharedPrefs, "key")
|
||||
|
||||
verify { listener(sharedPrefs, "key") }
|
||||
}
|
||||
}
|
|
@ -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.settings
|
||||
|
||||
import android.Manifest
|
||||
import io.mockk.MockKAnnotations
|
||||
import io.mockk.every
|
||||
import io.mockk.impl.annotations.MockK
|
||||
import mozilla.components.feature.sitepermissions.SitePermissions
|
||||
import mozilla.components.feature.sitepermissions.SitePermissions.Status
|
||||
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action
|
||||
import mozilla.components.support.test.robolectric.testContext
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mozilla.fenix.R
|
||||
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
|
||||
import org.mozilla.fenix.utils.Settings
|
||||
|
||||
@RunWith(FenixRobolectricTestRunner::class)
|
||||
class PhoneFeatureTest {
|
||||
|
||||
@MockK private lateinit var sitePermissions: SitePermissions
|
||||
@MockK private lateinit var settings: Settings
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
MockKAnnotations.init(this)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getStatus throws if both values are null`() {
|
||||
var exception: IllegalArgumentException? = null
|
||||
try {
|
||||
PhoneFeature.AUTOPLAY_AUDIBLE.getStatus()
|
||||
} catch (e: java.lang.IllegalArgumentException) {
|
||||
exception = e
|
||||
}
|
||||
assertNotNull(exception)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getStatus returns value from site permissions`() {
|
||||
every { sitePermissions.notification } returns Status.BLOCKED
|
||||
assertEquals(Status.BLOCKED, PhoneFeature.NOTIFICATION.getStatus(sitePermissions, settings))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getStatus returns value from settings`() {
|
||||
every {
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, Action.ALLOWED)
|
||||
} returns Action.ALLOWED
|
||||
assertEquals(Status.ALLOWED, PhoneFeature.AUTOPLAY_INAUDIBLE.getStatus(settings = settings))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getLabel() {
|
||||
assertEquals("Camera", PhoneFeature.CAMERA.getLabel(testContext))
|
||||
assertEquals("Location", PhoneFeature.LOCATION.getLabel(testContext))
|
||||
assertEquals("Microphone", PhoneFeature.MICROPHONE.getLabel(testContext))
|
||||
assertEquals("Notification", PhoneFeature.NOTIFICATION.getLabel(testContext))
|
||||
assertEquals("Autoplay", PhoneFeature.AUTOPLAY_AUDIBLE.getLabel(testContext))
|
||||
assertEquals("Autoplay", PhoneFeature.AUTOPLAY_INAUDIBLE.getLabel(testContext))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getPreferenceId() {
|
||||
assertEquals(R.string.pref_key_phone_feature_camera, PhoneFeature.CAMERA.getPreferenceId())
|
||||
assertEquals(R.string.pref_key_phone_feature_location, PhoneFeature.LOCATION.getPreferenceId())
|
||||
assertEquals(R.string.pref_key_phone_feature_microphone, PhoneFeature.MICROPHONE.getPreferenceId())
|
||||
assertEquals(R.string.pref_key_phone_feature_notification, PhoneFeature.NOTIFICATION.getPreferenceId())
|
||||
assertEquals(R.string.pref_key_browser_feature_autoplay_audible, PhoneFeature.AUTOPLAY_AUDIBLE.getPreferenceId())
|
||||
assertEquals(R.string.pref_key_browser_feature_autoplay_inaudible, PhoneFeature.AUTOPLAY_INAUDIBLE.getPreferenceId())
|
||||
|
||||
assertEquals(
|
||||
"pref_key_browser_feature_autoplay_inaudible",
|
||||
PhoneFeature.AUTOPLAY_INAUDIBLE.getPreferenceKey(testContext)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getAction returns value from settings`() {
|
||||
every {
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, Action.BLOCKED)
|
||||
} returns Action.ASK_TO_ALLOW
|
||||
assertEquals(Action.ASK_TO_ALLOW, PhoneFeature.AUTOPLAY_AUDIBLE.getAction(settings))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun findFeatureBy() {
|
||||
assertEquals(PhoneFeature.CAMERA, PhoneFeature.findFeatureBy(arrayOf(Manifest.permission.CAMERA)))
|
||||
assertEquals(PhoneFeature.MICROPHONE, PhoneFeature.findFeatureBy(arrayOf(Manifest.permission.RECORD_AUDIO)))
|
||||
}
|
||||
}
|
|
@ -3,5 +3,5 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
object AndroidComponents {
|
||||
const val VERSION = "48.0.20200625130125"
|
||||
const val VERSION = "48.0.20200626130049"
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue