1
0
Fork 0

Copione merged onto master
continuous-integration/drone/push Build is passing Details

master
blallo 2020-06-09 00:00:48 +02:00
commit 1aa2ee31a1
39 changed files with 2406 additions and 918 deletions

View File

@ -4,17 +4,20 @@
package org.mozilla.fenix
import android.os.StrictMode
import androidx.preference.PreferenceManager
import leakcanary.AppWatcher
import leakcanary.LeakCanary
import mozilla.components.support.ktx.android.os.resetAfter
import org.mozilla.fenix.ext.getPreferenceKey
class DebugFenixApplication : FenixApplication() {
override fun setupLeakCanary() {
val isEnabled = PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getPreferenceKey(R.string.pref_key_leakcanary), true)
val isEnabled = StrictMode.allowThreadDiskReads().resetAfter {
PreferenceManager.getDefaultSharedPreferences(this)
.getBoolean(getPreferenceKey(R.string.pref_key_leakcanary), true)
}
updateLeakCanaryState(isEnabled)
}

View File

@ -49,6 +49,8 @@ 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 mozilla.components.support.ktx.android.os.resetAfter
import org.mozilla.fenix.StrictModeManager.enableStrictMode
/**
*The main application class for Fenix. Records data to measure initialization performance.
@ -124,12 +126,13 @@ open class FenixApplication : LocaleAwareApplication() {
val megazordSetup = setupMegazord()
setDayNightTheme()
enableStrictMode()
enableStrictMode(true)
warmBrowsersCache()
// Make sure the engine is initialized and ready to use.
components.core.engine.warmUp()
StrictMode.allowThreadDiskReads().resetAfter {
components.core.engine.warmUp()
}
initializeWebExtensionSupport()
// Just to make sure it is impossible for any application-services pieces
@ -336,28 +339,6 @@ open class FenixApplication : LocaleAwareApplication() {
}
}
private fun enableStrictMode() {
if (Config.channel.isDebug) {
StrictMode.setThreadPolicy(
StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
.build()
)
var builder = StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.detectActivityLeaks()
.detectFileUriExposure()
.penaltyLog()
if (SDK_INT >= Build.VERSION_CODES.O) builder =
builder.detectContentUriWithoutPermission()
if (SDK_INT >= Build.VERSION_CODES.P) builder = builder.detectNonSdkApiUsage()
StrictMode.setVmPolicy(builder.build())
}
}
private fun initializeWebExtensionSupport() {
try {
GlobalAddonDependencyProvider.initialize(

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.StrictMode
import android.util.AttributeSet
import android.view.View
import android.view.WindowManager
@ -39,6 +40,7 @@ import mozilla.components.service.fxa.sync.SyncReason
import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.ktx.android.arch.lifecycle.addObservers
import mozilla.components.support.ktx.android.content.share
import mozilla.components.support.ktx.android.os.resetAfter
import mozilla.components.support.ktx.kotlin.isUrl
import mozilla.components.support.ktx.kotlin.toNormalizedUrl
import mozilla.components.support.locale.LocaleAwareAppCompatActivity
@ -122,7 +124,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
}
final override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
StrictModeManager.changeStrictModePolicies(supportFragmentManager)
// There is disk read violations on some devices such as samsung and pixel for android 9/10
StrictMode.allowThreadDiskReads().resetAfter {
super.onCreate(savedInstanceState)
}
components.publicSuffixList.prefetch()
@ -428,6 +434,12 @@ open class HomeActivity : LocaleAwareAppCompatActivity() {
browsingModeManager.mode = sessionMode
}
override fun attachBaseContext(base: Context) {
StrictMode.allowThreadDiskReads().resetAfter {
super.attachBaseContext(base)
}
}
protected open fun createBrowsingModeManager(initialMode: BrowsingMode): BrowsingModeManager {
return DefaultBrowsingModeManager(initialMode) { newMode ->
themeManager.currentTheme = newMode

View File

@ -0,0 +1,64 @@
/* 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
import android.os.Build
import android.os.StrictMode
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
/**
* Manages strict mode settings for the application.
*/
object StrictModeManager {
/***
* Enables strict mode for debug purposes. meant to be run only in the main process.
* @param setPenaltyDialog boolean value to decide setting the dialog box as a penalty.
*/
fun enableStrictMode(setPenaltyDialog: Boolean) {
if (Config.channel.isDebug) {
val threadPolicy = StrictMode.ThreadPolicy.Builder()
.detectAll()
.penaltyLog()
if (setPenaltyDialog) {
threadPolicy.penaltyDialog()
}
StrictMode.setThreadPolicy(threadPolicy.build())
var builder = StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.detectLeakedRegistrationObjects()
.detectActivityLeaks()
.detectFileUriExposure()
.penaltyLog()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) builder =
builder.detectContentUriWithoutPermission()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (setPenaltyDialog) {
builder.permitNonSdkApiUsage()
} else {
builder.detectNonSdkApiUsage()
}
}
StrictMode.setVmPolicy(builder.build())
}
}
/**
* Revert strict mode to disable penalty dialog. Tied to fragment lifecycle since strict mode
* needs to switch to penalty logs. Using the fragment life cycle allows decoupling from any
* specific fragment.
*/
fun changeStrictModePolicies(fragmentManager: FragmentManager) {
fragmentManager.registerFragmentLifecycleCallbacks(object :
FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentResumed(fm: FragmentManager, f: Fragment) {
enableStrictMode(false)
fm.unregisterFragmentLifecycleCallbacks(this)
}
}, false)
}
}

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.components
import android.content.Context
import android.os.StrictMode
import androidx.annotation.GuardedBy
import androidx.annotation.VisibleForTesting
import kotlinx.coroutines.CoroutineScope
@ -17,6 +18,7 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.lib.crash.CrashReporter
import mozilla.components.service.fxa.manager.FxaAccountManager
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.os.resetAfter
import kotlin.coroutines.CoroutineContext
/**
@ -75,7 +77,9 @@ class AccountAbnormalities(
private val logger = Logger("AccountAbnormalities")
private val prefs = context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE)
private val prefs = StrictMode.allowThreadDiskReads().resetAfter {
context.getSharedPreferences(PREF_FXA_ABNORMALITIES, Context.MODE_PRIVATE)
}
/**
* Once [accountManager] is initialized, queries it to detect abnormal account states.

View File

@ -5,6 +5,7 @@
package org.mozilla.fenix.components
import android.content.Context
import android.os.StrictMode
import androidx.lifecycle.LiveData
import androidx.paging.DataSource
import mozilla.components.browser.session.Session
@ -14,6 +15,7 @@ import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.tab.collections.TabCollectionStorage
import mozilla.components.support.base.observer.Observable
import mozilla.components.support.base.observer.ObserverRegistry
import mozilla.components.support.ktx.android.os.resetAfter
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.toShortUrl
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionViewHolder
@ -49,7 +51,9 @@ class TabCollectionStorage(
var cachedTabCollections = listOf<TabCollection>()
private val collectionStorage by lazy {
TabCollectionStorage(context, sessionManager)
StrictMode.allowThreadDiskReads().resetAfter {
TabCollectionStorage(context, sessionManager)
}
}
fun createCollection(title: String, sessions: List<Session>) {

View File

@ -10,6 +10,7 @@ import android.content.DialogInterface
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.StrictMode
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
@ -59,6 +60,7 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.ktx.android.os.resetAfter
import mozilla.components.support.ktx.android.util.dpToPx
import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.HomeActivity
@ -146,8 +148,10 @@ class HomeFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
if (!onboarding.userHasBeenOnboarded()) {
requireComponents.analytics.metrics.track(Event.OpenedAppFirstRun)
StrictMode.allowThreadDiskReads().resetAfter {
if (!onboarding.userHasBeenOnboarded()) {
requireComponents.analytics.metrics.track(Event.OpenedAppFirstRun)
}
}
}
@ -172,7 +176,9 @@ class HomeFragment : Fragment() {
collections = requireComponents.core.tabCollectionStorage.cachedTabCollections,
expandedCollections = emptySet(),
mode = currentMode.getCurrentMode(),
topSites = requireComponents.core.topSiteStorage.cachedTopSites,
topSites = StrictMode.allowThreadDiskReads().resetAfter {
requireComponents.core.topSiteStorage.cachedTopSites
},
tip = FenixTipManager(listOf(MigrationTipProvider(requireContext()))).getTip()
)
)

View File

@ -4,26 +4,33 @@
package org.mozilla.fenix.settings
import android.view.View
import android.widget.RadioButton
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.text.HtmlCompat
import androidx.preference.Preference
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.support.ktx.android.view.putCompoundDrawablesRelative
import org.mozilla.fenix.R
import org.mozilla.fenix.theme.ThemeManager
fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions {
return when (featurePhone) {
PhoneFeature.CAMERA -> copy(camera = camera.toggle())
PhoneFeature.LOCATION -> copy(location = location.toggle())
PhoneFeature.MICROPHONE -> copy(microphone = microphone.toggle())
PhoneFeature.NOTIFICATION -> copy(notification = notification.toggle())
PhoneFeature.AUTOPLAY_AUDIBLE -> copy(autoplayAudible = autoplayAudible.toggle())
PhoneFeature.AUTOPLAY_INAUDIBLE -> copy(autoplayInaudible = autoplayInaudible.toggle())
}
return update(featurePhone, get(featurePhone).toggle())
}
fun SitePermissions.get(field: PhoneFeature) = when (field) {
PhoneFeature.CAMERA -> camera
PhoneFeature.LOCATION -> location
PhoneFeature.MICROPHONE -> microphone
PhoneFeature.NOTIFICATION -> notification
PhoneFeature.AUTOPLAY_AUDIBLE -> autoplayAudible
PhoneFeature.AUTOPLAY_INAUDIBLE -> autoplayInaudible
}
fun SitePermissions.update(field: PhoneFeature, value: SitePermissions.Status) = when (field) {
PhoneFeature.CAMERA -> copy(camera = value)
PhoneFeature.LOCATION -> copy(location = value)
PhoneFeature.MICROPHONE -> copy(microphone = value)
PhoneFeature.NOTIFICATION -> copy(notification = value)
PhoneFeature.AUTOPLAY_AUDIBLE -> copy(autoplayAudible = value)
PhoneFeature.AUTOPLAY_INAUDIBLE -> copy(autoplayInaudible = value)
}
/**
@ -39,26 +46,6 @@ fun RadioButton.setStartCheckedIndicator() {
putCompoundDrawablesRelative(start = buttonDrawable)
}
fun initBlockedByAndroidView(phoneFeature: PhoneFeature, blockedByAndroidView: View) {
val context = blockedByAndroidView.context
if (!phoneFeature.isAndroidPermissionGranted(context)) {
blockedByAndroidView.visibility = View.VISIBLE
val descriptionLabel = blockedByAndroidView.findViewById<TextView>(R.id.blocked_by_android_feature_label)
val descriptionText = context.getString(
R.string.phone_feature_blocked_step_feature,
phoneFeature.getLabel(context)
)
descriptionLabel.text = HtmlCompat.fromHtml(descriptionText, HtmlCompat.FROM_HTML_MODE_COMPACT)
val permissionsLabel = blockedByAndroidView.findViewById<TextView>(R.id.blocked_by_android_permissions_label)
val permissionsText = context.getString(R.string.phone_feature_blocked_step_permissions)
permissionsLabel.text = HtmlCompat.fromHtml(permissionsText, HtmlCompat.FROM_HTML_MODE_COMPACT)
} else {
blockedByAndroidView.visibility = View.GONE
}
}
/**
* Sets the callback to be invoked when this preference is changed by the user (but before
* the internal state has been updated). Allows the type of the preference to be specified.

View File

@ -8,7 +8,9 @@ import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.Manifest.permission.RECORD_AUDIO
import android.content.Context
import android.os.Parcelable
import androidx.annotation.StringRes
import kotlinx.android.parcel.Parcelize
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.support.ktx.android.content.isPermissionGranted
@ -21,26 +23,17 @@ import org.mozilla.fenix.settings.sitepermissions.AUTOPLAY_BLOCK_AUDIBLE
import org.mozilla.fenix.utils.Settings
import android.Manifest.permission.CAMERA as CAMERA_PERMISSION
const val ID_CAMERA_PERMISSION = 0
const val ID_LOCATION_PERMISSION = 1
const val ID_MICROPHONE_PERMISSION = 2
const val ID_NOTIFICATION_PERMISSION = 3
const val ID_AUTOPLAY_AUDIBLE_PERMISSION = 4
const val ID_AUTOPLAY_INAUDIBLE_PERMISSION = 5
enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>) {
CAMERA(ID_CAMERA_PERMISSION, arrayOf(CAMERA_PERMISSION)),
LOCATION(ID_LOCATION_PERMISSION, arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)),
MICROPHONE(ID_MICROPHONE_PERMISSION, arrayOf(RECORD_AUDIO)),
NOTIFICATION(ID_NOTIFICATION_PERMISSION, emptyArray()),
AUTOPLAY_AUDIBLE(ID_AUTOPLAY_AUDIBLE_PERMISSION, emptyArray()),
AUTOPLAY_INAUDIBLE(ID_AUTOPLAY_INAUDIBLE_PERMISSION, emptyArray());
@Parcelize
enum class PhoneFeature(val androidPermissionsList: Array<String>) : Parcelable {
CAMERA(arrayOf(CAMERA_PERMISSION)),
LOCATION(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)),
MICROPHONE(arrayOf(RECORD_AUDIO)),
NOTIFICATION(emptyArray()),
AUTOPLAY_AUDIBLE(emptyArray()),
AUTOPLAY_INAUDIBLE(emptyArray());
fun isAndroidPermissionGranted(context: Context): Boolean {
return when (this) {
CAMERA, LOCATION, MICROPHONE -> context.isPermissionGranted(androidPermissionsList.asIterable())
NOTIFICATION, AUTOPLAY_AUDIBLE, AUTOPLAY_INAUDIBLE -> true
}
return context.isPermissionGranted(androidPermissionsList.asIterable())
}
@Suppress("ComplexMethod")
@ -78,7 +71,7 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
sitePermissions: SitePermissions? = null,
settings: Settings? = null
): SitePermissions.Status {
val status = getStatus(sitePermissions) ?: settings?.let(::getAction)?.toStatus()
val status = sitePermissions?.get(this) ?: settings?.let(::getAction)?.toStatus()
return requireNotNull(status)
}
@ -114,18 +107,6 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
}
}
private fun getStatus(sitePermissions: SitePermissions?): SitePermissions.Status? {
sitePermissions ?: return null
return when (this) {
CAMERA -> sitePermissions.camera
LOCATION -> sitePermissions.location
MICROPHONE -> sitePermissions.microphone
NOTIFICATION -> sitePermissions.notification
AUTOPLAY_AUDIBLE -> sitePermissions.autoplayAudible
AUTOPLAY_INAUDIBLE -> sitePermissions.autoplayInaudible
}
}
companion object {
fun findFeatureBy(permissions: Array<out String>): PhoneFeature? {
return values().find { feature ->

View File

@ -52,7 +52,7 @@ interface QuickSettingsController {
* Default behavior of [QuickSettingsController]. Other implementations are possible.
*
* @param context [Context] used for various Android interactions.
* @param quickSettingsStore [QuickSettingsFragmentStore] holding the [State] for all Views displayed
* @param quickSettingsStore [QuickSettingsFragmentStore] holding the State for all Views displayed
* in this Controller's Fragment.
* @param coroutineScope [CoroutineScope] used for structed concurrency.
* @param navController NavController] used for navigation.
@ -88,21 +88,20 @@ class DefaultQuickSettingsController(
}
override fun handlePermissionToggled(permission: WebsitePermission) {
val featureToggled = permission.getBackingFeature()
val featureToggled = permission.phoneFeature
when (permission.isBlockedByAndroid) {
true -> handleAndroidPermissionRequest(featureToggled.androidPermissionsList)
false -> {
val permissions = sitePermissions
if (permissions != null) {
val newPermissions = permissions.toggle(featureToggled).also {
handlePermissionsChange(it)
}
val newPermissions = permissions.toggle(featureToggled)
handlePermissionsChange(newPermissions)
sitePermissions = newPermissions
quickSettingsStore.dispatch(
WebsitePermissionAction.TogglePermission(
permission,
featureToggled,
featureToggled.getActionLabel(context, newPermissions, settings),
featureToggled.shouldBeEnabled(context, newPermissions, settings)
)
@ -117,7 +116,7 @@ class DefaultQuickSettingsController(
override fun handleAndroidPermissionGranted(feature: PhoneFeature) {
quickSettingsStore.dispatch(
WebsitePermissionAction.TogglePermission(
feature.getCorrespondingPermission(),
feature,
feature.getActionLabel(context, sitePermissions, settings),
feature.shouldBeEnabled(context, sitePermissions, settings)
)
@ -150,56 +149,6 @@ class DefaultQuickSettingsController(
}
}
/**
* Each [WebsitePermission] is mapped after a [PhoneFeature].
*
* Get this [WebsitePermission]'s [PhoneFeature].
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun WebsitePermission.getBackingFeature(): PhoneFeature = when (this) {
is WebsitePermission.Camera -> PhoneFeature.CAMERA
is WebsitePermission.Microphone -> PhoneFeature.MICROPHONE
is WebsitePermission.Notification -> PhoneFeature.NOTIFICATION
is WebsitePermission.Location -> PhoneFeature.LOCATION
is WebsitePermission.AutoplayAudible -> PhoneFeature.AUTOPLAY_AUDIBLE
is WebsitePermission.AutoplayInaudible -> PhoneFeature.AUTOPLAY_INAUDIBLE
}
/**
* Get the specific [WebsitePermission] implementation which this [PhoneFeature] is tied to.
*
* **The result only informs about the type of [WebsitePermission].
* The resulting object's properties are just stubs and not dependable.**
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun PhoneFeature.getCorrespondingPermission(): WebsitePermission {
val defaultStatus = ""
val defaultEnabled = false
val defaultVisible = false
val defaultBlockedByAndroid = false
return when (this) {
PhoneFeature.CAMERA -> WebsitePermission.Camera(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.LOCATION -> WebsitePermission.Location(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.MICROPHONE -> WebsitePermission.Microphone(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.NOTIFICATION -> WebsitePermission.Notification(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.AUTOPLAY_AUDIBLE -> WebsitePermission.AutoplayAudible(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
PhoneFeature.AUTOPLAY_INAUDIBLE -> WebsitePermission.AutoplayInaudible(
defaultStatus, defaultVisible, defaultEnabled, defaultBlockedByAndroid
)
}
}
/**
* Navigate to toggle [SitePermissions] for the specified [PhoneFeature]
*
@ -207,7 +156,7 @@ class DefaultQuickSettingsController(
*/
private fun navigateToManagePhoneFeature(phoneFeature: PhoneFeature) {
val directions = QuickSettingsSheetDialogFragmentDirections
.actionGlobalSitePermissionsManagePhoneFeature(phoneFeature.id)
.actionGlobalSitePermissionsManagePhoneFeature(phoneFeature)
navController.navigate(directions)
}
}

View File

@ -0,0 +1,38 @@
/* 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.quicksettings
import mozilla.components.lib.state.Action
import org.mozilla.fenix.settings.PhoneFeature
/**
* Parent [Action] for all the [QuickSettingsFragmentState] changes.
*/
sealed class QuickSettingsFragmentAction : Action
/**
* All possible [WebsiteInfoState] changes as result of user / system interactions.
*/
sealed class WebsiteInfoAction : QuickSettingsFragmentAction()
/**
* All possible [WebsitePermissionsState] changes as result of user / system interactions.
*/
sealed class WebsitePermissionAction : QuickSettingsFragmentAction() {
/**
* Change resulting from toggling a specific [WebsitePermission] for the current website.
*
* @param updatedFeature [PhoneFeature] backing a certain [WebsitePermission].
* Allows to easily identify which permission changed
* **Must be the name of one of the properties of [WebsitePermissionsState]**.
* @param updatedStatus [String] the new [WebsitePermission#status] which will be shown to the user.
* @param updatedEnabledStatus [Boolean] the new [WebsitePermission#enabled] which will be shown to the user.
*/
class TogglePermission(
val updatedFeature: PhoneFeature,
val updatedStatus: String,
val updatedEnabledStatus: Boolean
) : WebsitePermissionAction()
}

View File

@ -0,0 +1,50 @@
/* 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.quicksettings
/**
* Parent Reducer for all [QuickSettingsFragmentState]s of all Views shown in this Fragment.
*/
fun quickSettingsFragmentReducer(
state: QuickSettingsFragmentState,
action: QuickSettingsFragmentAction
): QuickSettingsFragmentState {
return when (action) {
is WebsiteInfoAction -> {
// There is no possible action that can change this View's state while it is displayed to the user.
// Every time the View is recreated it starts with a fresh state. This is the only way to display
// something different.
state
}
is WebsitePermissionAction -> state.copy(
websitePermissionsState = WebsitePermissionsStateReducer.reduce(
state.websitePermissionsState,
action
)
)
}
}
object WebsitePermissionsStateReducer {
/**
* Handles creating a new [WebsitePermissionsState] based on the specific [WebsitePermissionAction]
*/
fun reduce(
state: WebsitePermissionsState,
action: WebsitePermissionAction
): WebsitePermissionsState {
return when (action) {
is WebsitePermissionAction.TogglePermission -> {
val key = action.updatedFeature
val newWebsitePermission = state.getValue(key).copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
state + Pair(key, newWebsitePermission)
}
}
}
}

View File

@ -0,0 +1,79 @@
/* 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.quicksettings
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import mozilla.components.lib.state.State
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.PhoneFeature
/**
* [State] containing all data displayed to the user by this Fragment.
*
* Partitioned further to contain mutiple states for each standalone View this Fragment holds.
*/
data class QuickSettingsFragmentState(
val webInfoState: WebsiteInfoState,
val websitePermissionsState: WebsitePermissionsState
) : State
/**
* [State] to be rendered by [WebsiteInfoView] indicating whether the connection is secure or not.
*
* @param websiteUrl [String] the URL of the current web page.
* @param websiteTitle [String] the title of the current web page.
* @param websiteSecurityUiValues UI values to represent the security of the website.
*/
data class WebsiteInfoState(
val websiteUrl: String,
val websiteTitle: String,
val websiteSecurityUiValues: WebsiteSecurityUiValues,
val certificateName: String
) : State
enum class WebsiteSecurityUiValues(
@StringRes val securityInfoRes: Int,
@DrawableRes val iconRes: Int,
@ColorRes val iconTintRes: Int
) {
SECURE(
R.string.quick_settings_sheet_secure_connection,
R.drawable.mozac_ic_lock,
R.color.photonGreen50
),
INSECURE(
R.string.quick_settings_sheet_insecure_connection,
R.drawable.mozac_ic_globe,
R.color.photonRed50
)
}
/**
* [State] to be rendered by [WebsitePermissionsView] displaying all explicitly allowed or blocked
* website permissions.
*/
typealias WebsitePermissionsState = Map<PhoneFeature, WebsitePermission>
/**
* Wrapper over a website permission encompassing all it's needed state to be rendered on the screen.
*
* Contains a limited number of implementations because there is a known, finite number of permissions
* we need to display to the user.
*
* @property status The *allowed* / *blocked* permission status to be shown to the user.
* @property isVisible Whether this permission should be shown to the user.
* @property isEnabled Visual indication about whether this permission is *enabled* / *disabled*
* @property isBlockedByAndroid Whether the corresponding *dangerous* Android permission is granted
* for the app by the user or not.
*/
data class WebsitePermission(
val phoneFeature: PhoneFeature,
val status: String,
val isVisible: Boolean,
val isEnabled: Boolean,
val isBlockedByAndroid: Boolean
)

View File

@ -5,21 +5,18 @@
package org.mozilla.fenix.settings.quicksettings
import android.content.Context
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Reducer
import mozilla.components.lib.state.State
import mozilla.components.lib.state.Store
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.createStore
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeVisible
import org.mozilla.fenix.utils.Settings
import java.util.EnumMap
/**
* [QuickSettingsSheetDialogFragment]'s unique [Store].
@ -40,27 +37,6 @@ class QuickSettingsFragmentStore(
::quickSettingsFragmentReducer
) {
companion object {
/**
* String, Drawable & Drawable Tint color used to display that the current website connection is secured.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val getSecuredWebsiteUiValues = Triple(
R.string.quick_settings_sheet_secure_connection,
R.drawable.mozac_ic_lock,
R.color.photonGreen50
)
/**
* String, Drawable & Drawable Tint color used to display that the current website connection is
* **not** secured.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
val getInsecureWebsiteUiValues = Triple(
R.string.quick_settings_sheet_insecure_connection,
R.drawable.mozac_ic_globe,
R.color.photonRed50
)
/**
* Construct an initial [QuickSettingsFragmentState] for all Views displayed by the
* [QuickSettingsSheetDialogFragment].
@ -109,11 +85,8 @@ class QuickSettingsFragmentStore(
isSecured: Boolean,
certificateName: String
): WebsiteInfoState {
val (stringRes, iconRes, colorRes) = when (isSecured) {
true -> getSecuredWebsiteUiValues
false -> getInsecureWebsiteUiValues
}
return WebsiteInfoState(websiteUrl, websiteTitle, stringRes, iconRes, colorRes, certificateName)
val uiValues = if (isSecured) WebsiteSecurityUiValues.SECURE else WebsiteSecurityUiValues.INSECURE
return WebsiteInfoState(websiteUrl, websiteTitle, uiValues, certificateName)
}
/**
@ -132,26 +105,11 @@ class QuickSettingsFragmentStore(
permissions: SitePermissions?,
settings: Settings
): WebsitePermissionsState {
val cameraPermission =
PhoneFeature.CAMERA.toWebsitePermission(context, permissions, settings)
val microphonePermission =
PhoneFeature.MICROPHONE.toWebsitePermission(context, permissions, settings)
val notificationPermission =
PhoneFeature.NOTIFICATION.toWebsitePermission(context, permissions, settings)
val locationPermission =
PhoneFeature.LOCATION.toWebsitePermission(context, permissions, settings)
val autoplayAudiblePermission =
PhoneFeature.AUTOPLAY_AUDIBLE.toWebsitePermission(context, permissions, settings)
val autoplayInaudiblePermission =
PhoneFeature.AUTOPLAY_INAUDIBLE.toWebsitePermission(context, permissions, settings)
val shouldBeVisible = cameraPermission.isVisible || microphonePermission.isVisible ||
notificationPermission.isVisible || locationPermission.isVisible
return WebsitePermissionsState(
shouldBeVisible, cameraPermission, microphonePermission,
notificationPermission, locationPermission, autoplayAudiblePermission,
autoplayInaudiblePermission
)
val state = EnumMap<PhoneFeature, WebsitePermission>(PhoneFeature::class.java)
for (feature in PhoneFeature.values()) {
state[feature] = feature.toWebsitePermission(context, permissions, settings)
}
return state
}
/**
@ -163,425 +121,13 @@ class QuickSettingsFragmentStore(
permissions: SitePermissions?,
settings: Settings
): WebsitePermission {
val status = getPermissionStatus(context, permissions, settings)
return when (this) {
PhoneFeature.CAMERA -> WebsitePermission.Camera(
status.status, status.isVisible, status.isEnabled, status.isBlockedByAndroid
)
PhoneFeature.LOCATION -> WebsitePermission.Location(
status.status, status.isVisible, status.isEnabled, status.isBlockedByAndroid
)
PhoneFeature.MICROPHONE -> WebsitePermission.Microphone(
status.status, status.isVisible, status.isEnabled, status.isBlockedByAndroid
)
PhoneFeature.NOTIFICATION -> WebsitePermission.Notification(
status.status, status.isVisible, status.isEnabled, status.isBlockedByAndroid
)
PhoneFeature.AUTOPLAY_AUDIBLE -> WebsitePermission.AutoplayAudible(
status.status, status.isVisible, status.isEnabled, status.isBlockedByAndroid
)
PhoneFeature.AUTOPLAY_INAUDIBLE -> WebsitePermission.AutoplayInaudible(
status.status, status.isVisible, status.isEnabled, status.isBlockedByAndroid
)
}
}
/**
* Helper method for getting the [WebsitePermission] properties based on a specific [PhoneFeature].
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun PhoneFeature.getPermissionStatus(
context: Context,
permissions: SitePermissions?,
settings: Settings
) = PermissionStatus(
status = getActionLabel(context, permissions, settings),
isVisible = shouldBeVisible(permissions, settings),
isEnabled = shouldBeEnabled(context, permissions, settings),
isBlockedByAndroid = !isAndroidPermissionGranted(context)
)
/**
* Helper class acting as a temporary container of [WebsitePermission] properties.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
data class PermissionStatus(
val status: String,
val isVisible: Boolean,
val isEnabled: Boolean,
val isBlockedByAndroid: Boolean
)
}
}
// -------------------------------------------------------------------------------------------------
// States
// -------------------------------------------------------------------------------------------------
/**
* [State] containing all data displayed to the user by this Fragment.
*
* Partitioned further to contain mutiple states for each standalone View this Fragment holds.
*/
data class QuickSettingsFragmentState(
val webInfoState: WebsiteInfoState,
val websitePermissionsState: WebsitePermissionsState
) : State
/**
* [State] to be rendered by [WebsiteInfoView] indicating whether the connection is secure or not.
*
* @param websiteUrl [String] the URL of the current web page.
* @param websiteTitle [String] the title of the current web page.
* @param securityInfoRes [StringRes] for the connection description.
* @param iconRes [DrawableRes] image indicating the connection status.
* @param iconTintRes [ColorRes] icon color.
*/
data class WebsiteInfoState(
val websiteUrl: String,
val websiteTitle: String,
@StringRes val securityInfoRes: Int,
@DrawableRes val iconRes: Int,
@ColorRes val iconTintRes: Int,
val certificateName: String
) : State
/**
* /**
* [State] to be rendered by [WebsitePermissionsView] displaying all explicitly allowed or blocked
* website permissions.
*
* @param isVisible [Boolean] whether this contains data that needs to be displayed to the user.
* @param camera [WebsitePermission] containing all information about the *camera* permission.
* @param microphone [WebsitePermission] containing all information about the *microphone* permission.
* @param notification [notification] containing all information about the *notification* permission.
* @param location [WebsitePermission] containing all information about the *location* permission.
*/
*/
data class WebsitePermissionsState(
val isVisible: Boolean,
val camera: WebsitePermission,
val microphone: WebsitePermission,
val notification: WebsitePermission,
val location: WebsitePermission,
val autoplayAudible: WebsitePermission,
val autoplayInaudible: WebsitePermission
) : State
/**
* Wrapper over a website permission encompassing all it's needed state to be rendered on the screen.
*
* Contains a limited number of implementations because there is a known, finite number of permissions
* we need to display to the user.
*/
sealed class WebsitePermission {
/**
* The *allowed* / *blocked* permission status to be shown to the user.
*/
abstract val status: String
/**
* Whether this permission should be shown to the user.
*/
abstract val isVisible: Boolean
/**
* Visual indication about whether this permission is *enabled* / *disabled*
*/
abstract val isEnabled: Boolean
/**
* Whether the corresponding *dangerous* Android permission is granted for the app by the user or not.
*/
abstract val isBlockedByAndroid: Boolean
/**
* Helper method mimicking the default generated *copy()* method for a data class.
* Allows us using a familiar API in the reducer.
*/
abstract fun copy(
status: String = this.status,
isVisible: Boolean = this.isVisible,
isEnabled: Boolean = this.isEnabled,
isBlockedByAndroid: Boolean = this.isBlockedByAndroid
): WebsitePermission
/**
* Contains all information about the *camera* permission.
*/
data class Camera(
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean,
val name: String = "Camera" // helps to resolve the overload resolution ambiguity for the copy() method
) : WebsitePermission() {
override fun copy(
status: String,
isVisible: Boolean,
isEnabled: Boolean,
isBlockedByAndroid: Boolean
) = copy(
status = status,
isVisible = isVisible,
isEnabled = isEnabled,
isBlockedByAndroid = isBlockedByAndroid,
name = name
)
}
/**
* Contains all information about the *microphone* permission.
*/
data class Microphone(
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean,
val name: String = "Microphone" // helps to resolve the overload resolution ambiguity for the copy() method
) : WebsitePermission() {
override fun copy(
status: String,
isVisible: Boolean,
isEnabled: Boolean,
isBlockedByAndroid: Boolean
) = copy(
status = status,
isVisible = isVisible,
isEnabled = isEnabled,
isBlockedByAndroid = isBlockedByAndroid,
name = name
)
}
/**
* Contains all information about the *notification* permission.
*/
data class Notification(
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean,
val name: String = "Notification" // helps to resolve the overload resolution ambiguity for the copy() method
) : WebsitePermission() {
override fun copy(
status: String,
isVisible: Boolean,
isEnabled: Boolean,
isBlockedByAndroid: Boolean
) = copy(
status = status,
isVisible = isVisible,
isEnabled = isEnabled,
isBlockedByAndroid = isBlockedByAndroid,
name = name
)
}
/**
* Contains all information about the *location* permission.
*/
data class Location(
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean,
val name: String = "Location" // helps to resolve the overload resolution ambiguity for the copy() method
) : WebsitePermission() {
override fun copy(
status: String,
isVisible: Boolean,
isEnabled: Boolean,
isBlockedByAndroid: Boolean
) = copy(
status = status,
isVisible = isVisible,
isEnabled = isEnabled,
isBlockedByAndroid = isBlockedByAndroid,
name = name
)
}
/**
* Contains all information about the *autoplay audible* permission.
*/
data class AutoplayAudible(
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean,
val name: String = "AutoplayAudible" // helps to resolve the overload resolution ambiguity for the copy() method
) : WebsitePermission() {
override fun copy(
status: String,
isVisible: Boolean,
isEnabled: Boolean,
isBlockedByAndroid: Boolean
) = copy(
status = status,
isVisible = isVisible,
isEnabled = isEnabled,
isBlockedByAndroid = isBlockedByAndroid,
name = name
)
}
/**
* Contains all information about the *autoplay inaudible* permission.
*/
data class AutoplayInaudible(
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean,
// helps to resolve the overload resolution ambiguity for the copy() method
val name: String = "AutoplayInaudible"
) : WebsitePermission() {
override fun copy(
status: String,
isVisible: Boolean,
isEnabled: Boolean,
isBlockedByAndroid: Boolean
) = copy(
status = status,
isVisible = isVisible,
isEnabled = isEnabled,
isBlockedByAndroid = isBlockedByAndroid,
name = name
)
}
}
// -------------------------------------------------------------------------------------------------
// Actions
// -------------------------------------------------------------------------------------------------
/**
* Parent [Action] for all the [QuickSettingsFragmentState] changes.
*/
sealed class QuickSettingsFragmentAction : Action
/**
* All possible [WebsiteInfoState] changes as result of user / system interactions.
*/
sealed class WebsiteInfoAction : QuickSettingsFragmentAction()
/**
* All possible [WebsitePermissionsState] changes as result of user / system interactions.
*/
sealed class WebsitePermissionAction : QuickSettingsFragmentAction() {
/**
* Change resulting from toggling a specific [WebsitePermission] for the current website.
*
* @param updatedFeature [PhoneFeature] backing a certain [WebsitePermission].
* Allows to easily identify which permission changed
* **Must be the name of one of the properties of [WebsitePermissionsState]**.
* @param updatedStatus [String] the new [WebsitePermission#status] which will be shown to the user.
* @param updatedEnabledStatus [Boolean] the new [WebsitePermission#enabled] which will be shown to the user.
*/
class TogglePermission(
val websitePermission: WebsitePermission,
val updatedStatus: String,
val updatedEnabledStatus: Boolean
) : WebsitePermissionAction()
}
// -------------------------------------------------------------------------------------------------
// Reducers
// -------------------------------------------------------------------------------------------------
/**
* Parent [Reducer] for all [QuickSettingsFragmentState]s of all Views shown in this Fragment.
*/
fun quickSettingsFragmentReducer(
state: QuickSettingsFragmentState,
action: QuickSettingsFragmentAction
): QuickSettingsFragmentState {
return when (action) {
is WebsiteInfoAction -> state.copy(
webInfoState = WebsiteInfoStateReducer.reduce(
state.webInfoState,
action
return WebsitePermission(
phoneFeature = this,
status = getActionLabel(context, permissions, settings),
isVisible = shouldBeVisible(permissions, settings),
isEnabled = shouldBeEnabled(context, permissions, settings),
isBlockedByAndroid = !isAndroidPermissionGranted(context)
)
)
is WebsitePermissionAction -> state.copy(
websitePermissionsState = WebsitePermissionsStateReducer.reduce(
state.websitePermissionsState,
action
)
)
}
}
@Suppress("UNUSED_PARAMETER") // the action paramater is unused
object WebsiteInfoStateReducer {
/**
* Handles creating a new [WebsiteInfoState] based on the specific [WebsiteInfoAction]
*/
fun reduce(
state: WebsiteInfoState,
action: WebsiteInfoAction
): WebsiteInfoState {
// There is no possible action that can change this View's state while it is displayed to the user.
// Everytime the View is recreated it starts with a fresh state. This is the only way to display
// something different.
return state
}
}
object WebsitePermissionsStateReducer {
/**
* Handles creating a new [WebsitePermissionsState] based on the specific [WebsitePermissionAction]
*/
fun reduce(
state: WebsitePermissionsState,
action: WebsitePermissionAction
): WebsitePermissionsState {
return when (action) {
is WebsitePermissionAction.TogglePermission -> {
when (action.websitePermission) {
is WebsitePermission.Camera -> state.copy(
camera = state.camera.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
)
is WebsitePermission.Microphone -> state.copy(
microphone = state.microphone.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
)
is WebsitePermission.Notification -> state.copy(
notification = state.notification.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
)
is WebsitePermission.Location -> state.copy(
location = state.location.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
)
is WebsitePermission.AutoplayAudible -> {
return state.copy(
autoplayAudible = state.autoplayAudible.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
)
}
is WebsitePermission.AutoplayInaudible -> {
return state.copy(
autoplayInaudible = state.autoplayInaudible.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
)
}
}
}
}
}
}

View File

@ -7,14 +7,11 @@ package org.mozilla.fenix.settings.quicksettings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getColor
import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_website_info.view.*
import mozilla.components.support.ktx.android.content.getDrawableWithTint
import org.mozilla.fenix.R
/**
@ -38,7 +35,7 @@ class WebsiteInfoView(
fun update(state: WebsiteInfoState) {
bindUrl(state.websiteUrl)
bindTitle(state.websiteTitle)
bindSecurityInfo(state.securityInfoRes, state.iconRes, state.iconTintRes)
bindSecurityInfo(state.websiteSecurityUiValues)
bindCertificateName(state.certificateName)
}
@ -56,14 +53,11 @@ class WebsiteInfoView(
view.certificateInfo.isVisible = cert.isNotEmpty()
}
private fun bindSecurityInfo(
@StringRes securityInfoRes: Int,
@DrawableRes iconRes: Int,
@ColorRes iconTintRes: Int
) {
val icon = AppCompatResources.getDrawable(view.context, iconRes)
icon?.setTint(ContextCompat.getColor(view.context, iconTintRes))
view.securityInfo.setText(securityInfoRes)
view.securityInfoIcon.setImageResource(iconRes)
private fun bindSecurityInfo(uiValues: WebsiteSecurityUiValues) {
val tint = getColor(view.context, uiValues.iconTintRes)
view.securityInfo.setText(uiValues.securityInfoRes)
view.securityInfoIcon.setImageDrawable(
view.context.getDrawableWithTint(uiValues.iconRes, tint)
)
}
}

View File

@ -10,7 +10,10 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.isVisible
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.quicksettings_permissions.view.*
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.PhoneFeature
import java.util.EnumMap
/**
* Contract declaring all possible user interactions with [WebsitePermissionsView]
@ -49,26 +52,31 @@ class WebsitePermissionsView(
val view: View = LayoutInflater.from(context)
.inflate(R.layout.quicksettings_permissions, containerView, true)
private val permissionViews: Map<PhoneFeature, PermissionViewHolder> = EnumMap(mapOf(
PhoneFeature.CAMERA to PermissionViewHolder(view.cameraLabel, view.cameraStatus),
PhoneFeature.LOCATION to PermissionViewHolder(view.locationLabel, view.locationStatus),
PhoneFeature.MICROPHONE to PermissionViewHolder(view.microphoneLabel, view.microphoneStatus),
PhoneFeature.NOTIFICATION to PermissionViewHolder(view.notificationLabel, view.notificationStatus)
))
/**
* Allows changing what this View displays.
*
* @param state [WebsitePermissionsState] to be rendered.
*/
fun update(state: WebsitePermissionsState) {
if (state.isVisible) {
val isVisible = permissionViews.keys
.map { feature -> state.getValue(feature) }
.any { it.isVisible }
if (isVisible) {
interactor.onPermissionsShown()
}
// If more permissions are added into this View we can display them into a list
// and also use DiffUtil to only update one item in case of a permission change
bindPermission(state.camera,
Pair(view.findViewById(R.id.cameraLabel), view.findViewById(R.id.cameraStatus)))
bindPermission(state.location,
Pair(view.findViewById(R.id.locationLabel), view.findViewById(R.id.locationStatus)))
bindPermission(state.microphone,
Pair(view.findViewById(R.id.microphoneLabel), view.findViewById(R.id.microphoneStatus)))
bindPermission(state.notification,
Pair(view.findViewById(R.id.notificationLabel), view.findViewById(R.id.notificationStatus)))
for ((feature, views) in permissionViews) {
bindPermission(state.getValue(feature), views)
}
}
/**
@ -76,15 +84,15 @@ class WebsitePermissionsView(
* which will display permission's [icon, label, status] and register user inputs.
*
* @param permissionState [WebsitePermission] specific permission that can be shown to the user.
* @param permissionViews Views that will render [WebsitePermission]'s state.
* @param viewHolder Views that will render [WebsitePermission]'s state.
*/
private fun bindPermission(permissionState: WebsitePermission, permissionViews: Pair<TextView, TextView>) {
val (label, status) = permissionViews
status.text = permissionState.status
label.isEnabled = permissionState.isEnabled
label.isVisible = permissionState.isVisible
status.isVisible = permissionState.isVisible
status.setOnClickListener { interactor.onPermissionToggled(permissionState) }
private fun bindPermission(permissionState: WebsitePermission, viewHolder: PermissionViewHolder) {
viewHolder.label.isEnabled = permissionState.isEnabled
viewHolder.label.isVisible = permissionState.isVisible
viewHolder.status.text = permissionState.status
viewHolder.status.isVisible = permissionState.isVisible
viewHolder.status.setOnClickListener { interactor.onPermissionToggled(permissionState) }
}
data class PermissionViewHolder(val label: TextView, val status: TextView)
}

View File

@ -4,8 +4,13 @@
package org.mozilla.fenix.settings.sitepermissions
import android.view.View
import android.widget.TextView
import androidx.core.net.toUri
import androidx.core.text.HtmlCompat
import org.mozilla.fenix.R
import org.mozilla.fenix.components.Components
import org.mozilla.fenix.settings.PhoneFeature
/**
* Try to reload a session if a session with the given [origin] it is found.
@ -15,3 +20,23 @@ internal fun Components.tryReloadTabBy(origin: String) {
val session = core.sessionManager.all.find { it.url.toUri().host == origin }
useCases.sessionUseCases.reload(session)
}
internal fun initBlockedByAndroidView(phoneFeature: PhoneFeature, blockedByAndroidView: View) {
val context = blockedByAndroidView.context
if (!phoneFeature.isAndroidPermissionGranted(context)) {
blockedByAndroidView.visibility = View.VISIBLE
val descriptionLabel = blockedByAndroidView.findViewById<TextView>(R.id.blocked_by_android_feature_label)
val descriptionText = context.getString(
R.string.phone_feature_blocked_step_feature,
phoneFeature.getLabel(context)
)
descriptionLabel.text = HtmlCompat.fromHtml(descriptionText, HtmlCompat.FROM_HTML_MODE_COMPACT)
val permissionsLabel = blockedByAndroidView.findViewById<TextView>(R.id.blocked_by_android_permissions_label)
val permissionsText = context.getString(R.string.phone_feature_blocked_step_permissions)
permissionsLabel.text = HtmlCompat.fromHtml(permissionsText, HtmlCompat.FROM_HTML_MODE_COMPACT)
} else {
blockedByAndroidView.visibility = View.GONE
}
}

View File

@ -108,7 +108,7 @@ class SitePermissionsDetailsExceptionsFragment : PreferenceFragmentCompat() {
private fun navigateToPhoneFeature(phoneFeature: PhoneFeature) {
val directions =
SitePermissionsDetailsExceptionsFragmentDirections.actionSitePermissionsToExceptionsToManagePhoneFeature(
phoneFeatureId = phoneFeature.id,
phoneFeature = phoneFeature,
sitePermissions = sitePermissions
)
requireView().findNavController().navigate(directions)

View File

@ -77,7 +77,7 @@ class SitePermissionsFragment : PreferenceFragmentCompat() {
private fun navigateToPhoneFeature(phoneFeature: PhoneFeature) {
val directions = SitePermissionsFragmentDirections
.actionSitePermissionsToManagePhoneFeatures(phoneFeature.id)
.actionSitePermissionsToManagePhoneFeatures(phoneFeature)
Navigation.findNavController(requireView()).navigate(directions)
}
}

View File

@ -28,29 +28,22 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.initBlockedByAndroidView
import org.mozilla.fenix.settings.setStartCheckedIndicator
import org.mozilla.fenix.settings.update
@SuppressWarnings("TooManyFunctions")
class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment() {
private lateinit var phoneFeature: PhoneFeature
private lateinit var sitePermissions: SitePermissions
private lateinit var radioAllow: RadioButton
private lateinit var radioBlock: RadioButton
private lateinit var blockedByAndroidView: View
private val args by navArgs<SitePermissionsManageExceptionsPhoneFeatureFragmentArgs>()
val settings by lazy { requireContext().settings() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val args by navArgs<SitePermissionsManageExceptionsPhoneFeatureFragmentArgs>()
phoneFeature = args.phoneFeatureId.toPhoneFeature()
sitePermissions = args.sitePermissions
showToolbar(phoneFeature.getLabel(requireContext()))
showToolbar(args.phoneFeature.getLabel(requireContext()))
}
override fun onCreateView(
@ -71,7 +64,7 @@ class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment() {
override fun onResume() {
super.onResume()
initBlockedByAndroidView(phoneFeature, blockedByAndroidView)
initBlockedByAndroidView(args.phoneFeature, blockedByAndroidView)
}
private fun initAskToAllowRadio(rootView: View) {
@ -87,7 +80,7 @@ class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment() {
}
private fun RadioButton.restoreState(status: SitePermissions.Status) {
if (phoneFeature.getStatus(sitePermissions) == status) {
if (args.phoneFeature.getStatus(args.sitePermissions) == status) {
this.isChecked = true
this.setStartCheckedIndicator()
}
@ -109,7 +102,7 @@ class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment() {
setMessage(R.string.confirm_clear_permission_site)
setTitle(R.string.clear_permission)
setPositiveButton(android.R.string.yes) { dialog: DialogInterface, _ ->
val defaultStatus = phoneFeature.getStatus(settings = settings)
val defaultStatus = args.phoneFeature.getStatus(settings = settings)
updatedSitePermissions(defaultStatus)
resetRadioButtonsStatus(defaultStatus)
dialog.dismiss()
@ -147,23 +140,8 @@ class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment() {
startActivity(intent)
}
private fun Int.toPhoneFeature(): PhoneFeature {
return requireNotNull(PhoneFeature.values().find { feature ->
this == feature.id
}) {
"$this is a invalid PhoneFeature"
}
}
private fun updatedSitePermissions(status: SitePermissions.Status) {
val updatedSitePermissions = when (phoneFeature) {
PhoneFeature.CAMERA -> sitePermissions.copy(camera = status)
PhoneFeature.LOCATION -> sitePermissions.copy(location = status)
PhoneFeature.MICROPHONE -> sitePermissions.copy(microphone = status)
PhoneFeature.NOTIFICATION -> sitePermissions.copy(notification = status)
PhoneFeature.AUTOPLAY_AUDIBLE -> sitePermissions.copy(autoplayAudible = status)
PhoneFeature.AUTOPLAY_INAUDIBLE -> sitePermissions.copy(autoplayInaudible = status)
}
val updatedSitePermissions = args.sitePermissions.update(args.phoneFeature, status)
viewLifecycleOwner.lifecycleScope.launch(IO) {
requireComponents.core.permissionStorage.updateSitePermissions(updatedSitePermissions)
launch(Main) {

View File

@ -20,10 +20,8 @@ import android.view.ViewGroup
import android.widget.Button
import android.widget.RadioButton
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.fragment_manage_site_permissions_feature_phone.view.ask_to_allow_radio
import kotlinx.android.synthetic.main.fragment_manage_site_permissions_feature_phone.view.block_radio
import kotlinx.android.synthetic.main.fragment_manage_site_permissions_feature_phone.view.fourth_radio
import kotlinx.android.synthetic.main.fragment_manage_site_permissions_feature_phone.view.third_radio
import androidx.navigation.fragment.navArgs
import kotlinx.android.synthetic.main.fragment_manage_site_permissions_feature_phone.view.*
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ALLOWED
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ASK_TO_ALLOW
@ -31,10 +29,8 @@ import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.BL
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_AUDIBLE
import org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY_INAUDIBLE
import org.mozilla.fenix.settings.initBlockedByAndroidView
import org.mozilla.fenix.settings.setStartCheckedIndicator
import org.mozilla.fenix.utils.Settings
@ -45,19 +41,11 @@ const val AUTOPLAY_ALLOW_ALL = 3
@SuppressWarnings("TooManyFunctions")
class SitePermissionsManagePhoneFeatureFragment : Fragment() {
private lateinit var phoneFeature: PhoneFeature
private lateinit var settings: Settings
private val args by navArgs<SitePermissionsManagePhoneFeatureFragmentArgs>()
private val settings by lazy { requireContext().settings() }
private lateinit var blockedByAndroidView: View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
phoneFeature = SitePermissionsManagePhoneFeatureFragmentArgs
.fromBundle(requireArguments())
.permission.toPhoneFeature()
settings = requireContext().settings()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@ -80,13 +68,13 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
override fun onResume() {
super.onResume()
showToolbar(phoneFeature.getLabel(requireContext()))
initBlockedByAndroidView(phoneFeature, blockedByAndroidView)
showToolbar(args.phoneFeature.getLabel(requireContext()))
initBlockedByAndroidView(args.phoneFeature, blockedByAndroidView)
}
private fun initFirstRadio(rootView: View) {
with(rootView.ask_to_allow_radio) {
if (phoneFeature == AUTOPLAY_AUDIBLE) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
// Disabled because GV does not allow this setting. TODO Reenable after
// https://bugzilla.mozilla.org/show_bug.cgi?id=1621825 is fixed
// text = getString(R.string.preference_option_autoplay_allowed2)
@ -111,7 +99,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
private fun initSecondRadio(rootView: View) {
with(rootView.block_radio) {
if (phoneFeature == AUTOPLAY_AUDIBLE) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
text = getCombinedLabel(
getString(R.string.preference_option_autoplay_allowed_wifi_only2),
getString(R.string.preference_option_autoplay_allowed_wifi_subtext)
@ -135,7 +123,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
private fun initThirdRadio(rootView: View) {
with(rootView.third_radio) {
if (phoneFeature == AUTOPLAY_AUDIBLE) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
visibility = View.VISIBLE
text = getString(R.string.preference_option_autoplay_block_audio2)
setOnClickListener {
@ -150,7 +138,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
private fun initFourthRadio(rootView: View) {
with(rootView.fourth_radio) {
if (phoneFeature == AUTOPLAY_AUDIBLE) {
if (args.phoneFeature == AUTOPLAY_AUDIBLE) {
visibility = View.VISIBLE
text = getCombinedLabel(
getString(R.string.preference_option_autoplay_blocked3),
@ -167,7 +155,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
private fun RadioButton.restoreState(buttonAction: SitePermissionsRules.Action) {
if (phoneFeature.getAction(settings) == buttonAction) {
if (args.phoneFeature.getAction(settings) == buttonAction) {
this.isChecked = true
this.setStartCheckedIndicator()
}
@ -181,7 +169,7 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
private fun saveActionInSettings(action: SitePermissionsRules.Action) {
settings.setSitePermissionsPhoneFeatureAction(phoneFeature, action)
settings.setSitePermissionsPhoneFeatureAction(args.phoneFeature, action)
}
/**
@ -211,14 +199,6 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
initSettingsButton(blockedByAndroidView)
}
private fun Int.toPhoneFeature(): PhoneFeature {
return requireNotNull(PhoneFeature.values().find { feature ->
this == feature.id
}) {
"$this is a invalid PhoneFeature"
}
}
private fun initSettingsButton(rootView: View) {
val button = rootView.findViewById<Button>(R.id.settings_button)
button.setOnClickListener {

View File

@ -5,6 +5,8 @@ package org.mozilla.fenix.whatsnew
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import android.content.Context
import android.os.StrictMode
import mozilla.components.support.ktx.android.os.resetAfter
// This file is a modified port from Focus Android
@ -68,7 +70,9 @@ class WhatsNew private constructor(private val storage: WhatsNewStorage) {
fun shouldHighlightWhatsNew(context: Context): Boolean {
return shouldHighlightWhatsNew(
ContextWhatsNewVersion(context),
SharedPreferenceWhatsNewStorage(context)
StrictMode.allowThreadDiskReads().resetAfter {
SharedPreferenceWhatsNewStorage(context)
}
)
}

View File

@ -92,8 +92,8 @@
android:name="org.mozilla.fenix.settings.sitepermissions.SitePermissionsManagePhoneFeatureFragment"
tools:layout="@layout/fragment_manage_site_permissions_feature_phone">
<argument
android:name="permission"
app:argType="integer" />
android:name="phoneFeature"
app:argType="org.mozilla.fenix.settings.PhoneFeature" />
</fragment>
<fragment
@ -125,8 +125,8 @@
android:name="org.mozilla.fenix.settings.sitepermissions.SitePermissionsManageExceptionsPhoneFeatureFragment"
tools:layout="@layout/fragment_manage_site_permissions_feature_phone">
<argument
android:name="phoneFeatureId"
app:argType="integer" />
android:name="phoneFeature"
app:argType="org.mozilla.fenix.settings.PhoneFeature" />
<argument
android:name="sitePermissions"
app:argType="mozilla.components.feature.sitepermissions.SitePermissions" />

View File

@ -89,6 +89,8 @@
<string name="browser_menu_add_to_homescreen">Amestar a la pantalla d\'aniciu</string>
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
<string name="browser_menu_install_on_homescreen">Instalar</string>
<!-- Menu option on the toolbar that takes you to synced tabs page-->
<string name="synced_tabs">Llingüetes sincronizaes</string>
<!-- Browser menu button that opens the find in page menu -->
<string name="browser_menu_find_in_page">Alcontrar na páxina</string>
<!-- Browser menu button that creates a private tab -->
@ -377,6 +379,8 @@
<string name="library_desktop_bookmarks_unfiled">Otros marcadores</string>
<!-- Option in Library to open History page -->
<string name="library_history">Historial</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">Llingüetes sincronizaes</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">Llista de llectura</string>
<!-- Settings Page Title -->
@ -1139,6 +1143,9 @@
<!-- Summary for Accessibility Force Enable Zoom Preference -->
<string name="preference_accessibility_force_enable_zoom_summary">Activa l\'averamientu y alloñamientu, tamién en sitios web qu\'eviten estos xestos.</string>
<!-- Saved logins sorting strategy menu item -by name- (if selected, it will sort saved logins alphabetically) -->
<string name="saved_logins_sort_strategy_alphabetically">Nome (A-Z)</string>
<!-- Title of the Add search engine screen -->
<string name="search_engine_add_custom_search_engine_title">Amestar un motor de gueta</string>
<!-- Title of the Edit search engine screen -->
@ -1226,6 +1233,8 @@
<!-- Message in delete confirmation dialog for logins -->
<string name="login_deletion_confirmation">¿De xuru que quies desaniciar esti aniciu de sesión?</string>
<!-- Voice search button content description -->
<string name="voice_search_content_description">Gueta pela voz</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Fala agora</string>

View File

@ -0,0 +1,223 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">Не зараз</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">Новая картка</string>
<!-- Shortcut action to open new private tab -->
<string name="home_screen_shortcut_open_new_private_tab_2">Прыватная картка</string>
<!-- Heading for the Top Sites block -->
<string name="home_screen_top_sites_heading">Папулярныя сайты</string>
<!-- Browser Fragment -->
<!-- Content description (not visible, for screen readers etc.): Navigate to open tabs -->
<string name="browser_tabs_button">Адкрытыя карткі</string>
<!-- Content description (not visible, for screen readers etc.): Navigate backward (browsing history) -->
<string name="browser_menu_back">Назад</string>
<!-- Content description (not visible, for screen readers etc.): Navigate forward (browsing history) -->
<string name="browser_menu_forward">Наперад</string>
<!-- Content description (not visible, for screen readers etc.): Refresh current website -->
<string name="browser_menu_refresh">Абнавіць</string>
<!-- Content description (not visible, for screen readers etc.): Stop loading current website -->
<string name="browser_menu_stop">Спыніць</string>
<!-- Content description (not visible, for screen readers etc.): Bookmark the current page -->
<string name="browser_menu_bookmark">Дадаць у закладкі</string>
<!-- Content description (not visible, for screen readers etc.): Un-bookmark the current page -->
<string name="browser_menu_edit_bookmark">Рэдагаваць закладку</string>
<!-- Browser menu button that opens the addon manager -->
<string name="browser_menu_add_ons">Дадаткі</string>
<!-- Browser menu button that sends a user to help articles -->
<string name="browser_menu_help">Даведка</string>
<!-- Browser menu button that sends a to a the what's new article -->
<string name="browser_menu_whats_new">Што новага</string>
<!-- Browser menu button that opens the settings menu -->
<string name="browser_menu_settings">Налады</string>
<!-- Browser menu button that opens a user's library -->
<string name="browser_menu_library">Бібліятэка</string>
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
<string name="browser_menu_install_on_homescreen">Усталяваць</string>
<!-- Menu option on the toolbar that takes you to synced tabs page-->
<string name="synced_tabs">Сінхранізаваныя карткі</string>
<!-- Browser menu button that opens the find in page menu -->
<string name="browser_menu_find_in_page">Знайсці на старонцы</string>
<!-- Browser menu button that creates a private tab -->
<string name="browser_menu_private_tab">Прыватная картка</string>
<!-- Browser menu button that creates a new tab -->
<string name="browser_menu_new_tab">Новая картка</string>
<!-- Browser menu button that saves the current tab to a collection -->
<string name="browser_menu_save_to_collection_2">Захаваць у калекцыі</string>
<!-- Browser menu button that opens a dialog to report issues with the current site -->
<string name="browser_menu_report_issue">Паведаміць аб праблеме з сайтам</string>
<!-- Browser menu button that open a share menu to share the current site -->
<string name="browser_menu_share">Падзяліцца</string>
<!-- Search suggestion onboarding hint Learn more link text -->
<string name="search_suggestions_onboarding_learn_more_link">Даведацца больш</string>
<!-- Search Widget -->
<!-- Text preview for smaller sized widgets -->
<string name="search_widget_text_short">Пошук</string>
<!-- Text preview for larger sized widgets -->
<string name="search_widget_text_long">Пошук у інтэрнэце</string>
<!-- Content description (not visible, for screen readers etc.): Voice search -->
<string name="search_widget_voice">Галасавы пошук</string>
<!-- Preferences -->
<!-- Title for the settings page-->
<string name="settings">Налады</string>
<!-- Preference category for basic settings -->
<string name="preferences_category_basics">Асноўнае</string>
<!-- Preference category for general settings -->
<string name="preferences_category_general">Агульныя</string>
<!-- Preference category for all links about Fenix -->
<string name="preferences_category_about">Аб праграме</string>
<!-- Preference for settings related to changing the default search engine -->
<string name="preferences_default_search_engine">Прадвызначаны пашукавік</string>
<!-- Preference for settings related to Search -->
<string name="preferences_search">Пошук</string>
<!-- Preference for settings related to Search address bar -->
<string name="preferences_search_address_bar">Адрасны радок</string>
<!-- Preference linking to help about Fenix -->
<string name="preferences_help">Даведка</string>
<!-- Preference linking to about page for Fenix
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="preferences_about">Пра %1$s</string>
<!-- Preference linking to the your rights SUMO page -->
<string name="preferences_your_rights">Вашы правы</string>
<!-- Preference for settings related to saved passwords -->
<string name="preferences_passwords">Паролі</string>
<!-- Preference for settings related to saved credit cards and addresses -->
<string name="preferences_credit_cards_addresses">Крэдытныя карткі і адрасы</string>
<!-- Preference for settings related to changing the default browser -->
<string name="preferences_set_as_default_browser">Зрабіць прадвызначаным браўзерам</string>
<!-- Preference category for advanced settings -->
<string name="preferences_category_advanced">Дадаткова</string>
<!-- Preference category for privacy settings -->
<string name="preferences_category_privacy">Прыватнасць</string>
<!-- Preference category for privacy and security settings -->
<string name="preferences_category_privacy_security">Прыватнасць і бяспека</string>
<!-- Preference for advanced site permissions -->
<string name="preferences_site_permissions">Дазволы для сайтаў</string>
<!-- Preference for private browsing options -->
<string name="preferences_private_browsing_options">Прыватнае агляданне</string>
<!-- Preference for opening links in a private tab-->
<string name="preferences_open_links_in_a_private_tab">Адкрываць спасылкі ў прыватнай картцы</string>
<!-- Preference shown on banner to sign into account -->
<string name="preferences_sign_in">Увайсці</string>
<!-- Preference for changing where the toolbar is positioned -->
<string name="preferences_toolbar">Паліца прылад</string>
<!-- Preference for changing default theme to dark or light mode -->
<string name="preferences_theme">Тэма</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">Уладкаванне</string>
<!-- Preference description for banner about signing in -->
<string name="preferences_sign_in_description">Сінхранізуйце закладкі, гісторыю і шмат іншага з уліковым запісам Firefox</string>
<!-- Preference shown instead of account display name while account profile information isn't available yet. -->
<string name="preferences_account_default_name">Уліковы запіс Firefox</string>
<!-- Preference text for account title when there was an error syncing FxA -->
<string name="preferences_account_sync_error">Падключыцеся, каб аднавіць сінхранізацыю</string>
<!-- Preference for language -->
<string name="preferences_language">Мова</string>
<!-- Preference for data choices -->
<string name="preferences_data_choices">Выбар дадзеных</string>
<!-- Preference for data collection -->
<string name="preferences_data_collection">Збор дадзеных</string>
<!-- Preference linking to the privacy notice -->
<string name="preferences_privacy_link">Паведамленне аб прыватнасці</string>
<!-- Preference category for developer tools -->
<string name="developer_tools_category">Прылады распрацоўшчыка</string>
<!-- Preference for add_ons -->
<string name="preferences_addons">Дадаткі</string>
<!-- Account Preferences -->
<!-- Preference for triggering sync -->
<string name="preferences_sync_now">Сінхранізаваць</string>
<!-- Preference category for sync -->
<string name="preferences_sync_category">Выберыце, што сінхранізаваць</string>
<!-- Preference for syncing history -->
<string name="preferences_sync_history">Гісторыю</string>
<!-- Preference for syncing bookmarks -->
<string name="preferences_sync_bookmarks">Закладкі</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Лагіны</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">Карткі</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">Выйсці</string>
<!-- Preference displays and allows changing current FxA device name -->
<string name="preferences_sync_device_name">Назва прылады</string>
<!-- Option in library to open Bookmarks page -->
<string name="library_bookmarks">Закладкі</string>
<!-- Option in library to open Desktop Bookmarks "menu" page -->
<string name="library_desktop_bookmarks_menu">Меню закладак</string>
<!-- Option in library to open Desktop Bookmarks "toolbar" page -->
<string name="library_desktop_bookmarks_toolbar">Паліца закладак</string>
<!-- Option in library to open Desktop Bookmarks "unfiled" page -->
<string name="library_desktop_bookmarks_unfiled">Іншыя закладкі</string>
<!-- Option in Library to open History page -->
<string name="library_history">Гісторыя</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">Сінхранізаваныя карткі</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">Спіс для чытання</string>
<!-- Menu Item Label for Search in Library -->
<string name="library_search">Пошук</string>
<!-- Settings Page Title -->
<string name="settings_title">Налады</string>
<!-- Content description (not visible, for screen readers etc.): "Menu icon for items on a history item" -->
<string name="content_description_history_menu">Меню элемента гісторыі</string>
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Закрыць</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Адкрытыя карткі</string>
<!-- Title for the list of tabs in the current private session -->
<string name="tabs_header_private_title">Прыватны сеанс</string>
<!-- Title for the list of tabs in the current private session -->
<string name="tabs_header_private_tabs_title">Прыватныя карткі</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_tab">Дадаць картку</string>
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
<string name="remove_tab_from_collection">Выдаліць картку з калекцыі</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
<string name="close_tab">Закрыць картку</string>
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title -->
<string name="close_tab_title">Закрыць картку %s</string>
<!-- Open tabs menu item to close all tabs -->
<string name="tabs_menu_close_all_tabs">Закрыць усе карткі</string>
<!-- Open tabs menu item to share all tabs -->
<string name="tabs_menu_share_tabs">Падзяліцца карткамі</string>
<!-- Open tabs menu item to save tabs to collection -->
<string name="tabs_menu_save_to_collection">Захаваць у калекцыі</string>
<!-- Content description (not visible, for screen readers etc.): Opens the tab menu when pressed -->
<string name="tab_menu">Меню картак</string>
<!-- Tab menu item to share the tab -->
<string name="tab_share">Падзяліцца карткай</string>
<!-- Button in the current session menu. Deletes the session when pressed -->
<string name="current_session_delete">Выдаліць</string>
<!-- Button in the current session menu. Saves the session when pressed -->
<string name="current_session_save">Захаваць</string>
<!-- Button in the current session menu. Opens the share menu when pressed -->
<string name="current_session_share">Падзяліцца</string>
<!-- Content description (not visible, for screen readers etc.): Title icon for current session menu -->
<string name="current_session_image">Выява бягучага сеансу</string>
<!-- Button to save the current set of tabs into a collection -->
<string name="save_to_collection">Захаваць у калекцыі</string>
<!-- Text for the menu button to delete a collection -->
<string name="collection_delete">Выдаліць калекцыю</string>
<!-- Text for the menu button to rename a collection -->
<string name="collection_rename">Перайменаваць калекцыю</string>
<!-- Text for the button to open tabs of the selected collection -->
<string name="collection_open_tabs">Адкрыць карткі</string>
<!-- Text for the menu button to remove a top site -->
<string name="remove_top_site">Выдаліць</string>
</resources>

View File

@ -1416,4 +1416,14 @@
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Parlate avà</string>
</resources>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Cunnettatevi cù un contu Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Cunnettate un altru apparechju.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Ci vole à autenticassi torna.</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Ci vole à attivà a sincrunizazione di lunghjette.</string>
</resources>

View File

@ -96,6 +96,8 @@
<string name="browser_menu_add_to_homescreen">Agregar a pantalla de inicio</string>
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
<string name="browser_menu_install_on_homescreen">Instalar</string>
<!-- Menu option on the toolbar that takes you to synced tabs page-->
<string name="synced_tabs">Pestañas sincronizadas</string>
<!-- Browser menu button that opens the find in page menu -->
<string name="browser_menu_find_in_page">Buscar en la página</string>
<!-- Browser menu button that creates a private tab -->
@ -296,6 +298,8 @@
<string name="preferences_sync_bookmarks">Marcadores</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Inicios de sesión</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">Pestañas</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">Cerrar sesión</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -436,6 +440,8 @@
<string name="library_desktop_bookmarks_unfiled">Otros marcadores</string>
<!-- Option in Library to open History page -->
<string name="library_history">Historial</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">Pestañas sincronizadas</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">Lista de lectura</string>
<!-- Menu Item Label for Search in Library -->
@ -970,6 +976,8 @@
<string name="onboarding_tracking_protection_standard_button_description">Bloquea menos rastreadores pero permite que las páginas se carguen correctamente</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">Estricta (recomendada)</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_option">Estricto</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description">Bloquea más rastreadores para una mejor protección y rendimiento, pero es posible que algunos sitios no se carguen correctamente</string>
<!-- text for the toolbar position card header
@ -1418,4 +1426,15 @@
<string name="voice_search_content_description">Búsqueda por voz</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Hablar ahora</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Conectate con una cuenta de Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Conectar otro dispositivo.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Volver a autenticar.</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Habilitar sincronización de pestañas.</string>
</resources>

View File

@ -96,6 +96,8 @@
<string name="browser_menu_add_to_homescreen">Agregar a la pantalla de Inicio</string>
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
<string name="browser_menu_install_on_homescreen">Instalar</string>
<!-- Menu option on the toolbar that takes you to synced tabs page-->
<string name="synced_tabs">Pestañas sincronizadas</string>
<!-- Browser menu button that opens the find in page menu -->
<string name="browser_menu_find_in_page">Buscar en la página</string>
<!-- Browser menu button that creates a private tab -->
@ -296,6 +298,8 @@
<string name="preferences_sync_bookmarks">Marcadores</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Inicios de sesión</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">Pestañas</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">Cerrar sesión</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -433,6 +437,8 @@
<string name="library_desktop_bookmarks_unfiled">Otros marcadores</string>
<!-- Option in Library to open History page -->
<string name="library_history">Historial</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">Pestañas sincronizadas</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">Lista de lectura</string>
<!-- Menu Item Label for Search in Library -->
@ -982,6 +988,8 @@
<string name="onboarding_tracking_protection_standard_button_description">Bloquea menos rastreadores pero permite que las páginas carguen con normalidad</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_button">Estricta (recomendada)</string>
<!-- text for tracking protection radio button option for strict level of blocking -->
<string name="onboarding_tracking_protection_strict_option">Estricto</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description">Bloquea más rastreadores para una mejor protección y rendimiento, pero puede hacer que algunos sitios no funcionen correctamente</string>
<!-- text for the toolbar position card header
@ -1432,4 +1440,15 @@
<string name="voice_search_content_description">Búsqueda por voz</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Habla ahora</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Conectarse con una cuenta de Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Conectar otro dispositivo.</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Por favor, vuelve a autentificarte.</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Por favor, activa la sincronización de pestañas.</string>
</resources>

View File

@ -24,7 +24,7 @@
<string name="no_private_tabs_description">Yksityiset välilehdet näkyvät tässä.</string>
<!-- About content. The first parameter is the name of the application. (For example: Fenix) -->
<string name="about_content">%1$s on Mozillan tekemä.</string>
<string name="about_content">%1$s on Mozillan tuote.</string>
<!-- Private Browsing -->
<!-- Title for private session option -->
@ -1192,7 +1192,7 @@
<string name="add_to_homescreen_text_placeholder">Pikakuvakkeen nimi</string>
<!-- Describes the add to homescreen functionality -->
<string name="add_to_homescreen_description">Voit lisätä tämän sivuston puhelimesi kotinäytölle, jolloin sivuston käyttö onnistuu nopeasti ja tarjoaa sovelluksen kaltaisen kokemuksen.</string>
<string name="add_to_homescreen_description">Voit lisätä tämän sivuston puhelimesi aloitusnäytölle, jolloin sivuston käyttö onnistuu nopeasti ja tarjoaa sovelluksen kaltaisen kokemuksen.</string>
<!-- Preference for managing the settings for logins and passwords in Fenix -->
<string name="preferences_passwords_logins_and_passwords">Käyttäjätunnukset ja salasanat</string>
@ -1411,4 +1411,5 @@
<string name="voice_search_content_description">Äänihaku</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Puhu nyt</string>
</resources>
</resources>

View File

@ -96,6 +96,8 @@
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
<string name="browser_menu_install_on_homescreen">Տեղադրել</string>
<!-- Menu option on the toolbar that takes you to synced tabs page-->
<string name="synced_tabs">Համաժամեցված ներդիրներ</string>
<!-- Browser menu button that opens the find in page menu -->
<string name="browser_menu_find_in_page">Գտնել էջում</string>
<!-- Browser menu button that creates a private tab -->
@ -291,6 +293,8 @@
<string name="preferences_sync_bookmarks">Էջանիշեր</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">Մուտքանուններ</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">Ներդիրներ</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">Դուրս գրվել</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -423,6 +427,8 @@
<string name="library_desktop_bookmarks_unfiled">Այլ էջանիշեր</string>
<!-- Option in Library to open History page -->
<string name="library_history">Պատմություն</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">Համաժամեցված ներդիրներ</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">Ընթերցացուցակ</string>
<!-- Menu Item Label for Search in Library -->
@ -949,6 +955,8 @@
<string name="onboarding_tracking_protection_standard_button_description">Արգելափակում է ավելի քիչ հետագծիչներ, բայց թույլ է տալիս էջերը նորմալ բեռնել</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 -->
<string name="onboarding_tracking_protection_strict_option">Խիստ</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description">Ավելի լավ պաշտպանության և արտադրողականության համար արգելափակում է ավելի շատ հետագծիչներ, բայց կարող է պատճառ դառնալ, որ որոշ կայքեր պատշաճ չաշխատեն</string>
<!-- text for the toolbar position card header
@ -1397,4 +1405,15 @@
<string name="voice_search_content_description">Ձայնային որոնում</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Խոսել</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Կապակցել Firefox-ի հաշվով:</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Կապակցել այլ սարքի:</string>
<!-- Text displayed asking user to re-authenticate -->
<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">Խնդրում ենք միացնել ներդիրի համաժամացումը:</string>
</resources>

View File

@ -1440,6 +1440,14 @@
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">Parla ora</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Connetti un account Firefox.</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">Connetti un altro dispositivo.</string>
</resources>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">Esegui nuovamente laccesso.</string>
<!-- Text displayed when user has disabled tab syncing in Firefox Sync Account -->
<string name="synced_tabs_enable_tab_syncing">Attiva la sincronizzazione delle schede.</string>
</resources>

File diff suppressed because one or more lines are too long

View File

@ -156,6 +156,9 @@
<!-- Text preview for larger sized widgets -->
<string name="search_widget_text_long">ຄົ້ນຫາເວັບໄຊທ</string>
<!-- Content description (not visible, for screen readers etc.): Voice search -->
<string name="search_widget_voice">ຄົ້ນຫາດ້ວຍສຽງ</string>
<!-- Preferences -->
<!-- Title for the settings page-->
<string name="settings">ການຕັ້ງຄ່າ</string>
@ -165,6 +168,8 @@
<string name="preferences_category_general">ທົ່ວໄປ</string>
<!-- Preference category for all links about Fenix -->
<string name="preferences_category_about">ກ່ຽວກັບ</string>
<!-- Preference for settings related to changing the default search engine -->
<string name="preferences_default_search_engine">ເຄື່ອງມືການຄົ້ນຫາພື້ນຖານ</string>
<!-- Preference for settings related to Search -->
<string name="preferences_search">ຄົ້ນຫາ</string>
<!-- Preference for settings related to Search address bar -->
@ -184,6 +189,8 @@
<string name="preferences_passwords">ລະຫັດຜ່ານ</string>
<!-- Preference for settings related to saved credit cards and addresses -->
<string name="preferences_credit_cards_addresses">ບັດເຄດິດ ແລະ ທີ່ຢູ່</string>
<!-- Preference for settings related to changing the default browser -->
<string name="preferences_set_as_default_browser">ຕັ້ງໃຫ້ເປັນບຣາວເຊີພື້ນຖານ</string>
<!-- Preference category for advanced settings -->
<string name="preferences_category_advanced">ຂັ້ນສູງ</string>
<!-- Preference category for privacy settings -->
@ -194,6 +201,8 @@
<string name="preferences_private_browsing_options">ການທ່ອງເວັບແບບສ່ວນຕົວ</string>
<!-- Preference for opening links in a private tab-->
<string name="preferences_open_links_in_a_private_tab">ເປີດລີ້ງໃນແທັບສ່ວນຕົວ</string>
<!-- Preference for allowing screenshots to be taken while in a private tab-->
<string name="preferences_allow_screenshots_in_private_mode">ອະນຸຍາດໃຫ້ຖ່າຍຫນ້າຈໍໃນໂຫມດການທ່ອງເວັບແບບສ່ວນຕົວ</string>
<!-- Preference for adding private browsing shortcut -->
<string name="preferences_add_private_browsing_shortcut">ເພີ່ມທາງລັດການທ່ອງເວັບແບບສ່ວນຕົວ</string>
<!-- Preference for accessibility -->
@ -208,8 +217,12 @@
<string name="preferences_theme">ຊຸດປັບແຕ່ງ</string>
<!-- Preference for settings related to visual options -->
<string name="preferences_customize">ການປັບແຕ່ງ</string>
<!-- Preference description for banner about signing in -->
<string name="preferences_sign_in_description">Sync ບຸກມາກ, ປະຫວັດ, ແລະ ອື່ນໆດ້ວຍບັນຊີ Firefox ຂອງທ່ານ</string>
<!-- Preference shown instead of account display name while account profile information isn't available yet. -->
<string name="preferences_account_default_name">ບັນຊີ Firefox</string>
<!-- Preference text for account title when there was an error syncing FxA -->
<string name="preferences_account_sync_error">ເຊື່ອມຕໍຄືນເພື່ອເລີ່ມ sync ຄືນໃຫມ່</string>
<!-- Preference for language -->
<string name="preferences_language">ພາສາ</string>
@ -224,6 +237,10 @@
<!-- Preference for developers -->
<string name="preferences_remote_debugging">ການດີບັກທາງໄກຜ່ານທາງ USB</string>
<!-- Preference for account settings -->
<string name="preferences_account_settings">ການຕັ້ງ​ຄ່າ​ບັນ​ຊີ</string>
<!-- Preference for open links in third party apps -->
<string name="preferences_open_links_in_apps">ເປີດລີ້ງໃນແອັບ</string>
<!-- Preference for add_ons -->
<string name="preferences_addons">Add-ons</string>
@ -238,6 +255,8 @@
<string name="preferences_sync_bookmarks">ບຸກມາກ</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">ລັອກອິນ</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">ແທັບ</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">ອອກ​ຈາກ​ລະ​ບົບ</string>
<!-- Preference displays and allows changing current FxA device name -->

View File

@ -18,6 +18,9 @@
<!-- No Open Tabs Message Description -->
<string name="no_open_tabs_description">แท็บที่คุณเปิดจะถูกแสดงที่นี่</string>
<!-- No Private Tabs Message Description -->
<string name="no_private_tabs_description">แท็บส่วนตัวของคุณจะถูกแสดงที่นี่</string>
<!-- About content. The first parameter is the name of the application. (For example: Fenix) -->
<string name="about_content">%1$s ผลิตขึ้นโดย Mozilla</string>
@ -40,6 +43,14 @@
<!-- Text for the negative button -->
<string name="cfr_neg_button_text">ไม่ ขอบคุณ</string>
<!-- Search widget "contextual feature recommendation" (CFR) -->
<!-- Text for the main message. 'Firefox' intentionally hardcoded here.-->
<string name="search_widget_cfr_message">เข้า Firefox ได้เร็วขึ้นโดยเพิ่มวิดเจ็ตไปยังหน้าจอหลักของคุณ</string>
<!-- Text for the positive button -->
<string name="search_widget_cfr_pos_button_text">เพิ่มวิดเจ็ต</string>
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">ไม่ใช่ตอนนี้</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">แท็บใหม่</string>
@ -83,6 +94,8 @@
<string name="browser_menu_add_to_homescreen">เพิ่มไปยังหน้าจอหลัก</string>
<!-- Browser menu toggle that installs a Progressive Web App shortcut to the site on the device home screen. -->
<string name="browser_menu_install_on_homescreen">ติดตั้ง</string>
<!-- Menu option on the toolbar that takes you to synced tabs page-->
<string name="synced_tabs">แท็บที่ซิงค์</string>
<!-- Browser menu button that opens the find in page menu -->
<string name="browser_menu_find_in_page">ค้นหาในหน้า</string>
<!-- Browser menu button that creates a private tab -->
@ -250,6 +263,8 @@
<string name="preferences_show_search_shortcuts">แสดงทางลัดการค้นหา</string>
<!-- Preference title for switch preference to show search suggestions -->
<string name="preferences_show_search_suggestions">แสดงข้อเสนอแนะการค้นหา</string>
<!-- Preference title for switch preference to show voice search button -->
<string name="preferences_show_voice_search">แสดงการค้นหาด้วยเสียง</string>
<!-- Preference title for switch preference to show search suggestions also in private mode -->
<string name="preferences_show_search_suggestions_in_private">แสดงในวาระส่วนตัว</string>
<!-- Preference title for switch preference to show a clipboard suggestion when searching -->
@ -277,6 +292,8 @@
<string name="preferences_sync_bookmarks">ที่คั่นหน้า</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">การเข้าสู่ระบบ</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">แท็บ</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">ลงชื่อออก</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -366,6 +383,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[สแกนรหัส QR ที่แสดงใน <b>firefox.com/pair</b>]]></string>
<!-- Button to open camera for pairing -->
<string name="pair_open_camera">เปิดกล้อง</string>
<!-- Button to cancel pairing -->
@ -406,6 +426,8 @@
<string name="library_desktop_bookmarks_unfiled">ที่คั่นหน้าอื่น ๆ</string>
<!-- Option in Library to open History page -->
<string name="library_history">ประวัติ</string>
<!-- Option in Library to open Synced Tabs page -->
<string name="library_synced_tabs">แท็บที่ซิงค์</string>
<!-- Option in Library to open Reading List -->
<string name="library_reading_list">รายการอ่าน</string>
<!-- Menu Item Label for Search in Library -->
@ -426,6 +448,8 @@
<string name="tabs_header_private_tabs_title">แท็บส่วนตัว</string>
<!-- Content description (not visible, for screen readers etc.): Add tab button. Adds a news tab when pressed -->
<string name="add_tab">เพิ่มแท็บ</string>
<!-- Content description (not visible, for screen readers etc.): Removes tab from collection button. Removes the selected tab from collection when pressed -->
<string name="remove_tab_from_collection">เอาแท็บออกจากชุดสะสม</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
<string name="close_tab">ปิดแท็บ</string>
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title -->
@ -798,6 +822,8 @@
<!-- Title for Accessibility Text Size Scaling Preference -->
<string name="preference_accessibility_font_size_title">ขนาดแบบอักษร</string>
<!-- Title for Accessibility Text Automatic Size Scaling Preference -->
<string name="preference_accessibility_auto_size_2">การปรับขนาดแบบอักษรโดยอัตโนมัติ</string>
<!-- Summary for Accessibility Text Automatic Size Scaling Preference -->
<string name="preference_accessibility_auto_size_summary">ขนาดแบบอักษรจะตรงกับการตั้งค่า Android ของคุณ ปิดใช้งานเพื่อจัดการขนาดแบบอักษรที่นี่</string>
@ -855,6 +881,9 @@
<!-- Text for the snackbar to show the user that the deletion of browsing data is in progress -->
<string name="deleting_browsing_data_in_progress">กำลังลบข้อมูลการเรียกดู…</string>
<!-- Tips -->
<!-- text for firefox preview moving tip header "Firefox Preview" and "Firefox Nightly" are intentionally hardcoded -->
<string name="tip_firefox_preview_moved_header">ตอนนี้ Firefox Preview เป็น Firefox Nightly แล้ว</string>
<!-- text for firefox preview moving tip description -->
<string name="tip_firefox_preview_moved_description">Firefox Nightly อัปเดตทุกคืนและมีลูกเล่นใหม่ ๆ ทุกคืน
        อย่างไรก็ตามความเสถียรอาจลดลง ดาวน์โหลดเบราว์เซอร์เบต้าของเราเพื่อประสบการณ์ที่เสถียรยิ่งขึ้น</string>
@ -923,6 +952,8 @@
<string name="onboarding_tracking_protection_standard_button_description">ปิดกันตัวติดตามน้อยลง แต่อนุญาตให้โหลดหน้าได้ตามปกติ</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 -->
<string name="onboarding_tracking_protection_strict_option">เข้มงวด</string>
<!-- text for strict blocking option button description -->
<string name="onboarding_tracking_protection_strict_button_description">ปิดกันตัวติดตามมากขึ้นเพื่อการปกป้องและประสิทธิภาพที่ดีกว่า แต่ก็อาจทำให้บางไซต์ทำงานไม่สมบูรณ์</string>
<!-- text for the toolbar position card header
@ -1108,6 +1139,8 @@
<!-- About page link text to open support link -->
<string name="about_support">การสนับสนุน</string>
<!-- About page link text to list of past crashes (like about:crashes on desktop) -->
<string name="about_crashes">ข้อขัดข้อง</string>
<!-- About page link text to open privacy notice link -->
<string name="about_privacy_notice">ประกาศความเป็นส่วนตัว</string>
<!-- About page link text to open know your rights link -->
@ -1361,4 +1394,9 @@
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">พูดเลย</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">เชื่อมต่อกับบัญชี Firefox</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">เชื่อมต่ออุปกรณ์อื่น</string>
</resources>

View File

@ -184,4 +184,88 @@
<!-- Title for the settings page-->
<string name="settings">Nāgi\'iô\'</string>
<!-- Preference category for basic settings -->
<string name="preferences_category_basics">Dòj sā huāa</string>
<!-- Preference category for general settings -->
<string name="preferences_category_general">Da\' huā ngej</string>
<!-- Preference category for all links about Fenix -->
<string name="preferences_category_about">Rayi\'î</string>
<!-- Preference for settings related to changing the default search engine -->
<string name="preferences_default_search_engine">Sa nana\'ui\' nīka \'na\'</string>
<!-- Preference for settings related to Search -->
<string name="preferences_search">Nānà\'uì\'</string>
<!-- Preference for settings related to Search address bar -->
<string name="preferences_search_address_bar">Dukuán direksion</string>
<!-- Preference linking to help about Fenix -->
<string name="preferences_help">Sa rugûñu\'ūnj</string>
<!-- Preference link to rating Fenix on the Play Store -->
<string name="preferences_rate">Nāga\'uì\' du\'uej riña Google Play</string>
<!-- Preference for giving feedback about Fenix -->
<string name="preferences_feedback">Ga\'uì\' si nūguàn\'t</string>
<!-- Preference linking to about page for Fenix
The first parameter is the name of the app defined in app_name (for example: Fenix) -->
<string name="preferences_about">Rayi\'î %1$s</string>
<!-- Preference linking to the your rights SUMO page -->
<string name="preferences_your_rights">Sa tna\'uēj Rayi\'ît</string>
<!-- Preference for settings related to saved passwords -->
<string name="preferences_passwords">Nej da\'nga\' huìi</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">Nej rakïj ñaj</string>
<!-- Text displayed when there are no exceptions, with learn more link that brings users to a tracking protection SUMO page -->
<string name="exceptions_empty_message_learn_more_link">Gāhuin chrūn doj</string>
<!-- Option in Library to open Downloads page -->
<string name="library_downloads">Sa nadunïnjt</string>
<!-- Menu Item Label for Search in Library -->
<string name="library_search">Nānà\'uì\'</string>
<!-- Content description (not visible, for screen readers etc.): "Close button for library settings" -->
<string name="content_description_close_button">Nārán</string>
<!-- Sessions -->
<!-- Title for the list of tabs -->
<string name="tab_header_label">Nej Rakïj ñanj huā nî\'nïnj ïn</string>
<!-- Content description (not visible, for screen readers etc.): Close tab button. Closes the current session when pressed -->
<string name="close_tab">Nārán rakïj ñanj</string>
<!-- Content description (not visible, for screen readers etc.): Close tab <title> button. First parameter is tab title -->
<string name="close_tab_title">Nārán rakij ñanj %s</string>
<!-- Bookmark overflow menu share button -->
<string name="bookmark_menu_share_button">Dūyingô\'</string>
<!-- Bookmark URL editing field label -->
<string name="bookmark_url_label">URL</string>
<!-- Summary of tracking protection preference if tracking protection is set to on -->
<string name="tracking_protection_on">Nāchrūn</string>
<!-- Summary of tracking protection preference if tracking protection is set to off -->
<string name="tracking_protection_off">Dūnâ\àj</string>
<!-- Summary of delete browsing data on quit preference if it is set to on -->
<string name="delete_browsing_data_quit_on">Nāchrūn</string>
<!-- Summary of delete browsing data on quit preference if it is set to off -->
<string name="delete_browsing_data_quit_off">Dūnâ\àj</string>
<!-- Content description (not visible, for screen readers etc.): button to close the collection creator -->
<string name="create_collection_close">Nārán</string>
<!-- Content description (not visible, for screen readers etc.): Close onboarding screen -->
<string name="onboarding_close">Nārán</string>
<!-- Text displayed that links to website about enhanced tracking protection -->
<string name="preference_enhanced_tracking_protection_explanation_learn_more">Gāhuin chrūn doj</string>
<!-- Syncing saved logins in Fenix is on -->
<string name="preferences_passwords_sync_logins_on">Nāchrūn</string>
<!-- Syncing saved logins in Fenix is off -->
<string name="preferences_passwords_sync_logins_off">Dūnâ\àj</string>
<!-- Learn more link that will link to a page with more information displayed when a connection is insecure and we detect the user is entering a password -->
<string name="logins_insecure_connection_warning_learn_more">Gāhuin chrūn doj</string>
<!-- Placeholder text shown in the Search Engine Name TextField before a user enters text -->
<string name="search_add_custom_engine_name_hint">Si yugui</string>
</resources>

View File

@ -34,6 +34,9 @@
<!-- Text for the negative button -->
<string name="cfr_neg_button_text">نہیں شکریہ</string>
<!-- Text for the negative button -->
<string name="search_widget_cfr_neg_button_text">ابھی نہیں</string>
<!-- Home screen icons - Long press shortcuts -->
<!-- Shortcut action to open new tab -->
<string name="home_screen_shortcut_open_new_tab_2">نیا ٹیب</string>
@ -263,6 +266,8 @@
<string name="preferences_sync_bookmarks">بک مارکس</string>
<!-- Preference for syncing logins -->
<string name="preferences_sync_logins">لاگانز</string>
<!-- Preference for syncing tabs -->
<string name="preferences_sync_tabs">ٹیبس</string>
<!-- Preference for signing out -->
<string name="preferences_sign_out">سائن آؤٹ</string>
<!-- Preference displays and allows changing current FxA device name -->
@ -788,6 +793,8 @@
<!-- Title for the Delete browsing data preference -->
<string name="preferences_delete_browsing_data">براؤزنگ کا ڈیٹا مٹائیں</string>
<!-- Title for the tabs item in Delete browsing data -->
<string name="preferences_delete_browsing_data_tabs_title_2">ٹیبز کھولیں</string>
<!-- Subtitle for the tabs item in Delete browsing data, parameter will be replaced with the number of open tabs -->
<string name="preferences_delete_browsing_data_tabs_subtitle">%d ٹیب</string>
<!-- Title for the data and history items in Delete browsing data -->
@ -882,6 +889,8 @@
<string name="onboarding_tracking_protection_standard_button">معیاری</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 -->
<string name="onboarding_tracking_protection_strict_option">سخت</string>
<!-- text for the toolbar position card header
In English this is an idiom for "choose a side as in an argument or fight"
but it is ok to make this more literally about "choosing a position in a physical space -->
@ -1039,6 +1048,8 @@
<!-- About page link text to open support link -->
<string name="about_support">معاونت</string>
<!-- About page link text to list of past crashes (like about:crashes on desktop) -->
<string name="about_crashes">کریش</string>
<!-- About page link text to open privacy notice link -->
<string name="about_privacy_notice">رازداری کا نوٹس</string>
<!-- About page link text to open know your rights link -->
@ -1265,10 +1276,27 @@
<string name="login_deletion_confirmation">کیا آپ واقعی اس لاگ ان کو حذف کرنا چاہتے ہیں؟</string>
<!-- Positive action of a dialog asking to delete -->
<string name="dialog_delete_positive">حذف کریں</string>
<!-- The saved login options menu description. -->
<string name="login_options_menu">لاگ ان اختیارات</string>
<!-- The button description to save changes to an edited login. -->
<string name="save_changes_to_login">لاگ ان میں تبدیلیاں محفوظ کریں۔</string>
<!-- The button description to discard changes to an edited login. -->
<string name="discard_changes">تبدیلیاں ترک کریں</string>
<!-- The page title for editing a saved login. -->
<string name="edit">تدوین</string>
<!-- The error message in edit login view when password field is blank. -->
<string name="saved_login_password_required">پاس ورڈ کی درکار</string>
<!-- Voice search button content description -->
<string name="voice_search_content_description">آواز سے تلاش</string>
<!-- Voice search prompt description displayed after the user presses the voice search button -->
<string name="voice_search_explainer">اب بولیں</string>
<!-- Synced Tabs -->
<!-- Text displayed when user is not logged into a Firefox Account -->
<string name="synced_tabs_connect_to_sync_account">Firefox اکاؤنٹس کے ساتھ جوڑیں۔</string>
<!-- Text displayed to ask user to connect another device as no devices found with account -->
<string name="synced_tabs_connect_another_device">ایک اور آلہ جوڑیں۔</string>
<!-- Text displayed asking user to re-authenticate -->
<string name="synced_tabs_reauth">برائے مہربانی دوبارہ توثیق کریں۔</string>
</resources>

View File

@ -6,36 +6,33 @@ package org.mozilla.fenix.settings.quicksettings
import androidx.navigation.NavController
import androidx.navigation.NavDirections
import io.mockk.MockKMatcherScope
import io.mockk.Runs
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import io.mockk.verifyOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.browser.session.Session
import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.Status.NO_DECISION
import mozilla.components.feature.tabs.TabsUseCases
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertArrayEquals
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.PermissionStorage
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.toggle
import org.mozilla.fenix.utils.Settings
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(FenixRobolectricTestRunner::class)
@ -53,7 +50,7 @@ class DefaultQuickSettingsControllerTest {
private val requestPermissions = mockk<(Array<String>) -> Unit>(relaxed = true)
private val displayPermissions = mockk<() -> Unit>(relaxed = true)
private val dismiss = mockk<() -> Unit>(relaxed = true)
private val controller = DefaultQuickSettingsController(
private val controller = spyk(DefaultQuickSettingsController(
context = context,
quickSettingsStore = store,
coroutineScope = coroutinesScope,
@ -67,7 +64,7 @@ class DefaultQuickSettingsControllerTest {
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
)
))
@Test
fun `handlePermissionsShown should delegate to an injected parameter`() {
@ -81,58 +78,42 @@ class DefaultQuickSettingsControllerTest {
@Test
fun `handlePermissionToggled blocked by Android should handleAndroidPermissionRequest`() {
val cameraFeature = PhoneFeature.CAMERA
val websitePermission = mockk<WebsitePermission.Camera>()
val androidPermissions = slot<Array<String>>()
val websitePermission = mockk<WebsitePermission>()
every { websitePermission.phoneFeature } returns cameraFeature
every { websitePermission.isBlockedByAndroid } returns true
controller.handlePermissionToggled(websitePermission)
verify {
controller.handleAndroidPermissionRequest(capture(androidPermissions))
controller.handleAndroidPermissionRequest(eqArray(cameraFeature.androidPermissionsList))
}
assertTrue(androidPermissions.isCaptured)
assertArrayEquals(cameraFeature.androidPermissionsList, androidPermissions.captured)
}
@Test
@Ignore("Disabling because of intermittent failures https://github.com/mozilla-mobile/fenix/issues/8621")
fun `handlePermissionToggled allowed by Android should toggle the permissions and modify View's state`() {
val permissionName = "CAMERA"
val websitePermission = mockk<WebsitePermission.Camera>()
val toggledFeature = slot<PhoneFeature>()
val action = slot<WebsitePermissionAction>()
val websitePermission = mockk<WebsitePermission>()
every { websitePermission.phoneFeature } returns PhoneFeature.CAMERA
every { websitePermission.isBlockedByAndroid } returns false
every { websitePermission.name } returns permissionName
every { store.dispatch(any()) } returns mockk()
// For using the SitePermissions.toggle(..) extension method we need a static mock of SitePermissions.
mockkStatic("org.mozilla.fenix.settings.ExtensionsKt")
controller.handlePermissionToggled(websitePermission)
// We want to verify that the Status is toggled and this event is passed to Controller also.
assertSame(NO_DECISION, sitePermissions.camera)
verifyOrder {
val permission = sitePermissions.toggle(capture(toggledFeature))
controller.handlePermissionsChange(permission)
verify {
controller.handlePermissionsChange(sitePermissions.toggle(PhoneFeature.CAMERA))
}
// We should also modify View's state. Not necessarily as the last operation.
verify {
store.dispatch(capture(action))
store.dispatch(match { action ->
PhoneFeature.CAMERA == (action as WebsitePermissionAction.TogglePermission).updatedFeature
})
}
assertTrue(toggledFeature.isCaptured)
assertSame(PhoneFeature.CAMERA, toggledFeature.captured)
assertTrue(action.isCaptured)
assertEquals(WebsitePermissionAction.TogglePermission::class, action.captured::class)
assertEquals(websitePermission::class,
(action.captured as WebsitePermissionAction.TogglePermission).websitePermission::class)
}
@Test
fun `handlePermissionToggled blocked by user should navigate to site permission manager`() {
val websitePermission = mockk<WebsitePermission.Camera>()
val websitePermission = mockk<WebsitePermission>()
val invalidSitePermissionsController = DefaultQuickSettingsController(
context = context,
quickSettingsStore = store,
@ -149,6 +130,7 @@ class DefaultQuickSettingsControllerTest {
dismiss = dismiss
)
every { websitePermission.phoneFeature } returns PhoneFeature.CAMERA
every { websitePermission.isBlockedByAndroid } returns false
every { navController.navigate(any<NavDirections>()) } just Runs
@ -162,88 +144,44 @@ class DefaultQuickSettingsControllerTest {
@Test
fun `handleAndroidPermissionGranted should update the View's state`() {
val featureGranted = PhoneFeature.CAMERA
val permission = with(controller) {
featureGranted.getCorrespondingPermission()
}
val permissionStatus = featureGranted.getActionLabel(context, sitePermissions, appSettings)
val permissionEnabled =
featureGranted.shouldBeEnabled(context, sitePermissions, appSettings)
val action = slot<QuickSettingsFragmentAction>()
val permissionEnabled = featureGranted.shouldBeEnabled(context, sitePermissions, appSettings)
every { store.dispatch(any()) } returns mockk()
controller.handleAndroidPermissionGranted(featureGranted)
verify {
store.dispatch(capture(action))
store.dispatch(withArg { action ->
action as WebsitePermissionAction.TogglePermission
assertEquals(featureGranted, action.updatedFeature)
assertEquals(permissionStatus, action.updatedStatus)
assertEquals(permissionEnabled, action.updatedEnabledStatus)
})
}
assertTrue(action.isCaptured)
assertEquals(WebsitePermissionAction.TogglePermission::class, action.captured::class)
assertEquals(permission, (action.captured as WebsitePermissionAction.TogglePermission).websitePermission)
assertEquals(permissionStatus, (action.captured as WebsitePermissionAction.TogglePermission).updatedStatus)
assertEquals(permissionEnabled, (action.captured as WebsitePermissionAction.TogglePermission).updatedEnabledStatus)
}
@Test
fun `handleAndroidPermissionRequest should request from the injected callback`() {
val testPermissions = arrayOf("TestPermission")
val requiredPermissions = slot<Array<String>>()
// every { requestPermissions(capture(requiredPermissions)) } just Runs
controller.handleAndroidPermissionRequest(testPermissions)
verify { requestPermissions(capture(requiredPermissions)) }
assertTrue(requiredPermissions.isCaptured)
assertArrayEquals(testPermissions, requiredPermissions.captured)
verify { requestPermissions(eqArray(testPermissions)) }
}
@Test
@ExperimentalCoroutinesApi
@Ignore("Intermittently failing; https://github.com/mozilla-mobile/fenix/issues/8621")
fun `handlePermissionsChange should store the updated permission and reload webpage`() =
runBlocking {
val testPermissions = mockk<SitePermissions>()
val permissions = slot<SitePermissions>()
val session = slot<Session>()
fun `handlePermissionsChange should store the updated permission and reload webpage`() = coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
controller.handlePermissionsChange(testPermissions)
controller.handlePermissionsChange(testPermissions)
verifyOrder {
permissionStorage.updateSitePermissions(capture(permissions))
reload(capture(session))
}
assertTrue(permissions.isCaptured)
assertEquals(testPermissions, permissions.captured)
assertTrue(session.isCaptured)
assertEquals(browserSession, session.captured)
}
@Test
fun `WebsitePermission#getBackingFeature should return the PhoneFeature this permission is mapped from`() {
val cameraPermission = mockk<WebsitePermission.Camera>()
val microphonePermission = mockk<WebsitePermission.Microphone>()
val notificationPermission = mockk<WebsitePermission.Notification>()
val locationPermission = mockk<WebsitePermission.Location>()
with(controller) {
assertSame(PhoneFeature.CAMERA, cameraPermission.getBackingFeature())
assertSame(PhoneFeature.MICROPHONE, microphonePermission.getBackingFeature())
assertSame(PhoneFeature.NOTIFICATION, notificationPermission.getBackingFeature())
assertSame(PhoneFeature.LOCATION, locationPermission.getBackingFeature())
verifyOrder {
permissionStorage.updateSitePermissions(testPermissions)
reload(browserSession)
}
}
@Test
fun `PhoneFeature#getCorrespondingPermission should return the WebsitePermission which it maps to`() {
with(controller) {
assertEquals(WebsitePermission.Camera::class, PhoneFeature.CAMERA.getCorrespondingPermission()::class)
assertEquals(WebsitePermission.Microphone::class, PhoneFeature.MICROPHONE.getCorrespondingPermission()::class)
assertEquals(WebsitePermission.Notification::class, PhoneFeature.NOTIFICATION.getCorrespondingPermission()::class)
assertEquals(WebsitePermission.Location::class, PhoneFeature.LOCATION.getCorrespondingPermission()::class)
assertEquals(WebsitePermission.AutoplayAudible::class, PhoneFeature.AUTOPLAY_AUDIBLE.getCorrespondingPermission()::class)
assertEquals(WebsitePermission.AutoplayInaudible::class, PhoneFeature.AUTOPLAY_INAUDIBLE.getCorrespondingPermission()::class)
}
}
private inline fun <reified T> MockKMatcherScope.eqArray(value: Array<T>): Array<T> =
match { it contentEquals value }
}

View File

@ -24,9 +24,6 @@ import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.getInsecureWebsiteUiValues
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.getPermissionStatus
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.getSecuredWebsiteUiValues
import org.mozilla.fenix.settings.quicksettings.QuickSettingsFragmentStore.Companion.toWebsitePermission
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled
import org.mozilla.fenix.settings.quicksettings.ext.shouldBeVisible
@ -38,12 +35,6 @@ class QuickSettingsFragmentStoreTest {
private val context = spyk(testContext)
private val permissions = mockk<SitePermissions>()
private val appSettings = mockk<Settings>()
private val secureStringRes = R.string.quick_settings_sheet_secure_connection
private val secureDrawableRes = R.drawable.mozac_ic_lock
private val secureColorRes = R.color.photonGreen50
private val insecureStringRes = R.string.quick_settings_sheet_insecure_connection
private val insecureDrawableRes = R.drawable.mozac_ic_globe
private val insecureColorRes = R.color.photonRed50
@Test
fun `createStore constructs a QuickSettingsFragmentState`() {
@ -72,9 +63,7 @@ class QuickSettingsFragmentStoreTest {
assertNotNull(state)
assertSame(websiteUrl, state.websiteUrl)
assertSame(websiteTitle, state.websiteTitle)
assertEquals(secureStringRes, state.securityInfoRes)
assertEquals(secureDrawableRes, state.iconRes)
assertEquals(secureColorRes, state.iconTintRes)
assertEquals(WebsiteSecurityUiValues.SECURE, state.websiteSecurityUiValues)
}
@Test
@ -89,9 +78,7 @@ class QuickSettingsFragmentStoreTest {
assertNotNull(state)
assertSame(websiteUrl, state.websiteUrl)
assertSame(websiteTitle, state.websiteTitle)
assertEquals(insecureStringRes, state.securityInfoRes)
assertEquals(insecureDrawableRes, state.iconRes)
assertEquals(insecureColorRes, state.iconTintRes)
assertEquals(WebsiteSecurityUiValues.INSECURE, state.websiteSecurityUiValues)
}
@Test
@ -118,12 +105,12 @@ class QuickSettingsFragmentStoreTest {
// Just need to know that the WebsitePermissionsState properties are initialized.
// Making sure they are correctly initialized is tested in the `initWebsitePermission` test.
assertNotNull(state)
assertNotNull(state.camera)
assertNotNull(state.microphone)
assertNotNull(state.notification)
assertNotNull(state.location)
assertNotNull(state.autoplayAudible)
assertNotNull(state.autoplayInaudible)
assertNotNull(state[PhoneFeature.CAMERA])
assertNotNull(state[PhoneFeature.MICROPHONE])
assertNotNull(state[PhoneFeature.NOTIFICATION])
assertNotNull(state[PhoneFeature.LOCATION])
assertNotNull(state[PhoneFeature.AUTOPLAY_AUDIBLE])
assertNotNull(state[PhoneFeature.AUTOPLAY_INAUDIBLE])
}
@Test
@ -142,7 +129,7 @@ class QuickSettingsFragmentStoreTest {
val websitePermission = cameraFeature.toWebsitePermission(context, permissions, appSettings)
assertNotNull(websitePermission)
assertEquals(WebsitePermission.Camera::class, websitePermission::class)
assertEquals(cameraFeature, websitePermission.phoneFeature)
assertEquals(allowedStatus, websitePermission.status)
assertTrue(websitePermission.isVisible)
assertTrue(websitePermission.isEnabled)
@ -154,7 +141,7 @@ class QuickSettingsFragmentStoreTest {
val phoneFeature = PhoneFeature.CAMERA
every { permissions.camera } returns SitePermissions.Status.NO_DECISION
val permissionsStatus = phoneFeature.getPermissionStatus(context, permissions, appSettings)
val permissionsStatus = phoneFeature.toWebsitePermission(context, permissions, appSettings)
verify {
// Verifying phoneFeature.getActionLabel gets "Status(child of #2#4).ordinal()) was not called"
@ -174,12 +161,6 @@ class QuickSettingsFragmentStoreTest {
@ExperimentalCoroutinesApi
fun `TogglePermission should only modify status and visibility of a specific WebsitePermissionsState`() =
runBlocking {
val cameraPermissionName = "Camera"
val microphonePermissionName = "Microphone"
val notificationPermissionName = "Notification"
val locationPermissionName = "Location"
val autoplayAudiblePermissionName = "AutoplayAudible"
val autoplayInaudiblePermissionName = "AutoplayInaudible"
val initialCameraStatus = "initialCameraStatus"
val initialMicStatus = "initialMicStatus"
val initialNotificationStatus = "initialNotificationStatus"
@ -192,31 +173,37 @@ class QuickSettingsFragmentStoreTest {
val defaultEnabledStatus = true
val defaultBlockedByAndroidStatus = true
val websiteInfoState = mockk<WebsiteInfoState>()
val initialWebsitePermissionsState = WebsitePermissionsState(
val baseWebsitePermission = WebsitePermission(
phoneFeature = PhoneFeature.CAMERA,
status = "",
isVisible = true,
camera = WebsitePermission.Camera(
initialCameraStatus, defaultVisibilityStatus,
defaultEnabledStatus, defaultBlockedByAndroidStatus, cameraPermissionName
isEnabled = true,
isBlockedByAndroid = true
)
val initialWebsitePermissionsState = mapOf(
PhoneFeature.CAMERA to baseWebsitePermission.copy(
phoneFeature = PhoneFeature.CAMERA,
status = initialCameraStatus
),
microphone = WebsitePermission.Microphone(
initialMicStatus, defaultVisibilityStatus,
defaultEnabledStatus, defaultBlockedByAndroidStatus, microphonePermissionName
PhoneFeature.MICROPHONE to baseWebsitePermission.copy(
phoneFeature = PhoneFeature.MICROPHONE,
status = initialMicStatus
),
notification = WebsitePermission.Notification(
initialNotificationStatus, defaultVisibilityStatus,
defaultEnabledStatus, defaultBlockedByAndroidStatus, notificationPermissionName
PhoneFeature.NOTIFICATION to baseWebsitePermission.copy(
phoneFeature = PhoneFeature.NOTIFICATION,
status = initialNotificationStatus
),
location = WebsitePermission.Location(
initialLocationStatus, defaultVisibilityStatus,
defaultEnabledStatus, defaultBlockedByAndroidStatus, locationPermissionName
PhoneFeature.LOCATION to baseWebsitePermission.copy(
phoneFeature = PhoneFeature.LOCATION,
status = initialLocationStatus
),
autoplayAudible = WebsitePermission.AutoplayAudible(
initialAutoplayAudibleStatus, defaultVisibilityStatus,
defaultEnabledStatus, defaultBlockedByAndroidStatus, autoplayAudiblePermissionName
PhoneFeature.AUTOPLAY_AUDIBLE to baseWebsitePermission.copy(
phoneFeature = PhoneFeature.AUTOPLAY_AUDIBLE,
status = initialAutoplayAudibleStatus
),
autoplayInaudible = WebsitePermission.AutoplayInaudible(
initialAutoplayInaudibleStatus, defaultVisibilityStatus,
defaultEnabledStatus, defaultBlockedByAndroidStatus, autoplayInaudiblePermissionName
PhoneFeature.AUTOPLAY_INAUDIBLE to baseWebsitePermission.copy(
phoneFeature = PhoneFeature.AUTOPLAY_INAUDIBLE,
status = initialAutoplayInaudibleStatus
)
)
val initialState = QuickSettingsFragmentState(
@ -226,7 +213,7 @@ class QuickSettingsFragmentStoreTest {
store.dispatch(
WebsitePermissionAction.TogglePermission(
mockk<WebsitePermission.Microphone>(),
PhoneFeature.MICROPHONE,
updatedMicrophoneStatus,
updatedMicrophoneEnabledStatus
)
@ -237,53 +224,35 @@ class QuickSettingsFragmentStoreTest {
assertNotSame(initialWebsitePermissionsState, store.state.websitePermissionsState)
assertSame(websiteInfoState, store.state.webInfoState)
assertNotNull(store.state.websitePermissionsState.camera)
assertEquals(cameraPermissionName, (store.state.websitePermissionsState.camera as WebsitePermission.Camera).name)
assertEquals(initialCameraStatus, store.state.websitePermissionsState.camera.status)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.camera.isVisible)
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.camera.isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.camera.isBlockedByAndroid)
assertNotNull(store.state.websitePermissionsState[PhoneFeature.CAMERA])
assertEquals(PhoneFeature.CAMERA, store.state.websitePermissionsState.getValue(PhoneFeature.CAMERA).phoneFeature)
assertEquals(initialCameraStatus, store.state.websitePermissionsState.getValue(PhoneFeature.CAMERA).status)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.getValue(PhoneFeature.CAMERA).isVisible)
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.getValue(PhoneFeature.CAMERA).isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.getValue(PhoneFeature.CAMERA).isBlockedByAndroid)
assertNotNull(store.state.websitePermissionsState.microphone)
assertEquals(microphonePermissionName, (store.state.websitePermissionsState.microphone as WebsitePermission.Microphone).name)
assertNotNull(store.state.websitePermissionsState[PhoneFeature.MICROPHONE])
assertEquals(PhoneFeature.MICROPHONE, store.state.websitePermissionsState.getValue(PhoneFeature.MICROPHONE).phoneFeature)
// Only the following two properties must have been changed!
assertEquals(updatedMicrophoneStatus, store.state.websitePermissionsState.microphone.status)
assertEquals(updatedMicrophoneEnabledStatus, store.state.websitePermissionsState.microphone.isEnabled)
assertEquals(updatedMicrophoneStatus, store.state.websitePermissionsState.getValue(PhoneFeature.MICROPHONE).status)
assertEquals(updatedMicrophoneEnabledStatus, store.state.websitePermissionsState.getValue(PhoneFeature.MICROPHONE).isEnabled)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.microphone.isVisible)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.microphone.isBlockedByAndroid)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.getValue(PhoneFeature.MICROPHONE).isVisible)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.getValue(PhoneFeature.MICROPHONE).isBlockedByAndroid)
assertNotNull(store.state.websitePermissionsState.notification)
assertEquals(notificationPermissionName, (store.state.websitePermissionsState.notification as WebsitePermission.Notification).name)
assertEquals(initialNotificationStatus, store.state.websitePermissionsState.notification.status)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.notification.isVisible)
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.notification.isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.notification.isBlockedByAndroid)
assertNotNull(store.state.websitePermissionsState[PhoneFeature.NOTIFICATION])
assertEquals(PhoneFeature.NOTIFICATION, store.state.websitePermissionsState.getValue(PhoneFeature.NOTIFICATION).phoneFeature)
assertEquals(initialNotificationStatus, store.state.websitePermissionsState.getValue(PhoneFeature.NOTIFICATION).status)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.getValue(PhoneFeature.NOTIFICATION).isVisible)
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.getValue(PhoneFeature.NOTIFICATION).isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.getValue(PhoneFeature.NOTIFICATION).isBlockedByAndroid)
assertNotNull(store.state.websitePermissionsState.location)
assertEquals(locationPermissionName, (store.state.websitePermissionsState.location as WebsitePermission.Location).name)
assertEquals(initialLocationStatus, store.state.websitePermissionsState.location.status)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.location.isVisible)
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.location.isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.location.isBlockedByAndroid)
assertNotNull(store.state.websitePermissionsState[PhoneFeature.LOCATION])
assertEquals(PhoneFeature.LOCATION, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).phoneFeature)
assertEquals(initialLocationStatus, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).status)
assertEquals(defaultVisibilityStatus, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).isVisible)
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).isBlockedByAndroid)
}
@Test
fun `getSecuredWebsiteUiValues() should return the right values`() {
val uiValues = getSecuredWebsiteUiValues
assertEquals(secureStringRes, uiValues.first)
assertEquals(secureDrawableRes, uiValues.second)
assertEquals(secureColorRes, uiValues.third)
}
@Test
fun `getInsecureWebsiteUiValues() should return the right values`() {
val uiValues = getInsecureWebsiteUiValues
assertEquals(insecureStringRes, uiValues.first)
assertEquals(insecureDrawableRes, uiValues.second)
assertEquals(insecureColorRes, uiValues.third)
}
}

View File

@ -3,5 +3,5 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
object AndroidComponents {
const val VERSION = "45.0.20200605130045"
const val VERSION = "45.0.20200607130045"
}