1
0
Fork 0

Copione merged onto master

master
blallo 2020-06-27 00:01:03 +02:00
commit fb2f9ccd70
40 changed files with 539 additions and 128 deletions

View File

@ -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: |

View File

@ -50,8 +50,8 @@ class HomeScreenTest {
// Verify Top Sites
verifyExistingTopSitesList()
verifyExistingTopSitesTabs("Wikipedia")
verifyExistingTopSitesTabs("YouTube")
verifyExistingTopSitesTabs("Top Articles")
verifyExistingTopSitesTabs("Google")
}
}

View File

@ -148,7 +148,7 @@ class TopSitesTest {
val defaultTopSites = arrayOf(
"Top Articles",
"Wikipedia",
"YouTube"
"Google"
)
homeScreen { }.dismissOnboarding()

View File

@ -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,

View File

@ -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
}
}

View File

@ -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)

View File

@ -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(

View File

@ -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"
}

View File

@ -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"
}

View File

@ -70,7 +70,7 @@ class DefaultToolbarMenu(
onAddonsManagerTapped = {
onItemTapped.invoke(ToolbarMenu.Item.AddonsManager)
},
appendExtensionActionAtStart = !shouldReverseItems
appendExtensionSubMenuAtStart = !shouldReverseItems
)
}

View File

@ -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 {

View File

@ -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)
}
}
}

View File

@ -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,

View File

@ -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)
)

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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"),

View File

@ -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

View File

@ -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

View File

@ -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)
}
}

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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>

View File

@ -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>

View File

@ -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 &quot;Sair&quot; 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 \&quot;Sair\&quot; 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>

View File

@ -874,8 +874,6 @@
<string name="preference_summary_delete_browsing_data_on_quit">ลบข้อมูลการเรียกดูโดยอัตโนมัติเมื่อคุณเลือก &quot;ออก&quot; จากเมนูหลัก</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">ลบข้อมูลการเรียกดูโดยอัตโนมัติเมื่อคุณเลือก \&quot;ออก\&quot; จากเมนูหลัก</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>

View File

@ -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>

View File

@ -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 -->

View File

@ -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>

View File

@ -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 {

View File

@ -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)
}
}

View File

@ -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") }
}
}

View File

@ -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)))
}
}

View File

@ -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