For #16847: Allow autoplay to controlled via the toolbar.

upstream-sync
Arturo Mejia 4 years ago committed by Sebastian Kaspari
parent 6f3665bc74
commit 00d971e9d3

@ -126,8 +126,6 @@ import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration
import java.lang.ref.WeakReference
import mozilla.components.feature.media.fullscreen.MediaFullscreenOrientationFeature
import org.mozilla.fenix.FeatureFlags.newMediaSessionApi
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.QuickSettingsSheetDialogFragmentDirections
/**
* Base fragment extended by [BrowserFragment].
@ -368,7 +366,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
}
browserToolbarView.view.display.setOnPermissionIndicatorClickedListener {
navigateToAutoplaySetting()
showQuickSettingsDialog()
}
browserToolbarView.view.display.setOnTrackingProtectionClickedListener {
@ -1054,7 +1052,7 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
}
protected abstract fun navToQuickSettingsSheet(
session: Session,
tab: SessionState,
sitePermissions: SitePermissions?
)
@ -1082,15 +1080,15 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
* which lets the user control tracking protection and site settings.
*/
private fun showQuickSettingsDialog() {
val session = getSessionById() ?: return
val tab = getCurrentTab() ?: return
viewLifecycleOwner.lifecycleScope.launch(Main) {
val sitePermissions: SitePermissions? = session.url.toUri().host?.let { host ->
val sitePermissions: SitePermissions? = tab.content.url.toUri().host?.let { host ->
val storage = requireComponents.core.permissionStorage
storage.findSitePermissionsBy(host)
}
view?.let {
navToQuickSettingsSheet(session, sitePermissions)
navToQuickSettingsSheet(tab, sitePermissions)
}
}
}
@ -1124,6 +1122,10 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
}
}
protected fun getCurrentTab(): SessionState? {
return requireComponents.core.store.state.findCustomTabOrSelectedTab(customTabSessionId)
}
private suspend fun bookmarkTapped(sessionUrl: String, sessionTitle: String) = withContext(IO) {
val bookmarksStorage = requireComponents.core.bookmarksStorage
val existing =
@ -1293,10 +1295,4 @@ abstract class BaseBrowserFragment : Fragment(), UserInteractionHandler,
browserToolbarView.setScrollFlags(enabled)
}
}
private fun navigateToAutoplaySetting() {
val directions = QuickSettingsSheetDialogFragmentDirections
.actionGlobalSitePermissionsManagePhoneFeature(PhoneFeature.AUTOPLAY_AUDIBLE)
findNavController().navigate(directions)
}
}

@ -20,6 +20,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.thumbnails.BrowserThumbnails
import mozilla.components.browser.toolbar.BrowserToolbar
import mozilla.components.feature.app.links.AppLinksUseCases
@ -220,16 +221,17 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
return readerViewFeature.onBackPressed() || super.onBackPressed()
}
override fun navToQuickSettingsSheet(session: Session, sitePermissions: SitePermissions?) {
override fun navToQuickSettingsSheet(tab: SessionState, sitePermissions: SitePermissions?) {
val directions =
BrowserFragmentDirections.actionBrowserFragmentToQuickSettingsSheetDialogFragment(
sessionId = session.id,
url = session.url,
title = session.title,
isSecured = session.securityInfo.secure,
sessionId = tab.id,
url = tab.content.url,
title = tab.content.title,
isSecured = tab.content.securityInfo.secure,
sitePermissions = sitePermissions,
gravity = getAppropriateLayoutGravity(),
certificateName = session.securityInfo.issuer
certificateName = tab.content.securityInfo.issuer,
permissionHighlights = tab.content.permissionHighlights
)
nav(R.id.browserFragment, directions)
}

@ -14,6 +14,7 @@ import mozilla.components.feature.addons.migration.DefaultSupportedAddonsChecker
import mozilla.components.feature.addons.migration.SupportedAddonsChecker
import mozilla.components.feature.addons.update.AddonUpdater
import mozilla.components.feature.addons.update.DefaultAddonUpdater
import mozilla.components.feature.sitepermissions.SitePermissionsStorage
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
import mozilla.components.support.migration.state.MigrationStore
import org.mozilla.fenix.BuildConfig
@ -21,6 +22,7 @@ import org.mozilla.fenix.Config
import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.perf.StrictModeManager
import org.mozilla.fenix.components.metrics.AppStartupTelemetry
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.perf.lazyMonitored
import org.mozilla.fenix.utils.ClipboardHandler
@ -126,6 +128,10 @@ class Components(private val context: Context) {
AddonManager(core.store, core.engine, addonCollectionProvider, addonUpdater)
}
val sitePermissionsStorage by lazyMonitored {
SitePermissionsStorage(context, context.components.core.engine)
}
val analytics by lazyMonitored { Analytics(context) }
val publicSuffixList by lazyMonitored { PublicSuffixList(context) }
val clipboardHandler by lazyMonitored { ClipboardHandler(context) }

@ -5,46 +5,33 @@
package org.mozilla.fenix.components
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.paging.DataSource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.Status
import mozilla.components.feature.sitepermissions.SitePermissionsStorage
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.utils.Mockable
import kotlin.coroutines.CoroutineContext
@Mockable
class PermissionStorage(private val context: Context) {
val permissionsStorage by lazy {
SitePermissionsStorage(context, context.components.core.engine)
}
fun addSitePermissionException(
origin: String,
location: Status,
notification: Status,
microphone: Status,
camera: Status
): SitePermissions {
val sitePermissions = SitePermissions(
origin = origin,
location = location,
camera = camera,
microphone = microphone,
notification = notification,
savedAt = System.currentTimeMillis()
)
class PermissionStorage(
private val context: Context,
@VisibleForTesting internal val dispatcher: CoroutineContext = Dispatchers.IO,
@VisibleForTesting internal val permissionsStorage: SitePermissionsStorage =
context.components.sitePermissionsStorage
) {
suspend fun add(sitePermissions: SitePermissions) = withContext(dispatcher) {
permissionsStorage.save(sitePermissions)
return sitePermissions
}
suspend fun findSitePermissionsBy(origin: String): SitePermissions? = withContext(Dispatchers.IO) {
suspend fun findSitePermissionsBy(origin: String): SitePermissions? = withContext(dispatcher) {
permissionsStorage.findSitePermissionsBy(origin)
}
suspend fun updateSitePermissions(sitePermissions: SitePermissions) = withContext(Dispatchers.IO) {
suspend fun updateSitePermissions(sitePermissions: SitePermissions) = withContext(dispatcher) {
permissionsStorage.update(sitePermissions)
}
@ -52,11 +39,11 @@ class PermissionStorage(private val context: Context) {
return permissionsStorage.getSitePermissionsPaged()
}
suspend fun deleteSitePermissions(sitePermissions: SitePermissions) = withContext(Dispatchers.IO) {
suspend fun deleteSitePermissions(sitePermissions: SitePermissions) = withContext(dispatcher) {
permissionsStorage.remove(sitePermissions)
}
suspend fun deleteAllSitePermissions() = withContext(Dispatchers.IO) {
suspend fun deleteAllSitePermissions() = withContext(dispatcher) {
permissionsStorage.removeAll()
}
}

@ -15,6 +15,7 @@ import kotlinx.android.synthetic.main.component_browser_top_toolbar.*
import kotlinx.android.synthetic.main.fragment_browser.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import mozilla.components.browser.session.Session
import mozilla.components.browser.state.state.SessionState
import mozilla.components.concept.engine.manifest.WebAppManifestParser
import mozilla.components.concept.engine.manifest.getOrNull
import mozilla.components.feature.contextmenu.ContextMenuCandidate
@ -34,7 +35,6 @@ import org.mozilla.fenix.browser.CustomTabContextMenuCandidate
import org.mozilla.fenix.browser.FenixSnackbarDelegate
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
@ -181,16 +181,17 @@ class ExternalAppBrowserFragment : BaseBrowserFragment(), UserInteractionHandler
return customTabsIntegration.onBackPressed() || super.removeSessionIfNeeded()
}
override fun navToQuickSettingsSheet(session: Session, sitePermissions: SitePermissions?) {
override fun navToQuickSettingsSheet(tab: SessionState, sitePermissions: SitePermissions?) {
val directions = ExternalAppBrowserFragmentDirections
.actionGlobalQuickSettingsSheetDialogFragment(
sessionId = session.id,
url = session.url,
title = session.title,
isSecured = session.securityInfo.secure,
sessionId = tab.id,
url = tab.content.url,
title = tab.content.title,
isSecured = tab.content.securityInfo.secure,
sitePermissions = sitePermissions,
gravity = getAppropriateLayoutGravity(),
certificateName = session.securityInfo.issuer
certificateName = tab.content.securityInfo.issuer,
permissionHighlights = tab.content.permissionHighlights
)
nav(R.id.externalAppBrowserFragment, directions)
}

@ -19,23 +19,28 @@ fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions {
}
fun SitePermissions.get(field: PhoneFeature) = when (field) {
PhoneFeature.AUTOPLAY ->
throw IllegalAccessException("AUTOPLAY can't be accessed via get try " +
"using AUTOPLAY_AUDIBLE and AUTOPLAY_INAUDIBLE")
PhoneFeature.CAMERA -> camera
PhoneFeature.LOCATION -> location
PhoneFeature.MICROPHONE -> microphone
PhoneFeature.NOTIFICATION -> notification
PhoneFeature.AUTOPLAY_AUDIBLE -> autoplayAudible
PhoneFeature.AUTOPLAY_INAUDIBLE -> autoplayInaudible
PhoneFeature.AUTOPLAY_AUDIBLE -> autoplayAudible.toStatus()
PhoneFeature.AUTOPLAY_INAUDIBLE -> autoplayInaudible.toStatus()
PhoneFeature.PERSISTENT_STORAGE -> localStorage
PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS -> mediaKeySystemAccess
}
fun SitePermissions.update(field: PhoneFeature, value: SitePermissions.Status) = when (field) {
PhoneFeature.AUTOPLAY -> throw IllegalAccessException("AUTOPLAY can't be accessed via update " +
"try using AUTOPLAY_AUDIBLE and AUTOPLAY_INAUDIBLE")
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)
PhoneFeature.AUTOPLAY_AUDIBLE -> copy(autoplayAudible = value.toAutoplayStatus())
PhoneFeature.AUTOPLAY_INAUDIBLE -> copy(autoplayInaudible = value.toAutoplayStatus())
PhoneFeature.PERSISTENT_STORAGE -> copy(localStorage = value)
PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS -> copy(mediaKeySystemAccess = value)
}

@ -29,6 +29,7 @@ enum class PhoneFeature(val androidPermissionsList: Array<String>) : Parcelable
LOCATION(arrayOf(ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION)),
MICROPHONE(arrayOf(RECORD_AUDIO)),
NOTIFICATION(emptyArray()),
AUTOPLAY(emptyArray()),
AUTOPLAY_AUDIBLE(emptyArray()),
AUTOPLAY_INAUDIBLE(emptyArray()),
PERSISTENT_STORAGE(emptyArray()),
@ -82,7 +83,8 @@ enum class PhoneFeature(val androidPermissionsList: Array<String>) : Parcelable
NOTIFICATION -> context.getString(R.string.preference_phone_feature_notification)
PERSISTENT_STORAGE -> context.getString(R.string.preference_phone_feature_persistent_storage)
MEDIA_KEY_SYSTEM_ACCESS -> context.getString(R.string.preference_phone_feature_media_key_system_access)
AUTOPLAY_AUDIBLE, AUTOPLAY_INAUDIBLE -> context.getString(R.string.preference_browser_feature_autoplay)
AUTOPLAY, AUTOPLAY_AUDIBLE, AUTOPLAY_INAUDIBLE ->
context.getString(R.string.preference_browser_feature_autoplay)
}
}
@ -97,6 +99,7 @@ enum class PhoneFeature(val androidPermissionsList: Array<String>) : Parcelable
LOCATION -> R.string.pref_key_phone_feature_location
MICROPHONE -> R.string.pref_key_phone_feature_microphone
NOTIFICATION -> R.string.pref_key_phone_feature_notification
AUTOPLAY -> R.string.pref_key_browser_feature_autoplay_audible
AUTOPLAY_AUDIBLE -> R.string.pref_key_browser_feature_autoplay_audible
AUTOPLAY_INAUDIBLE -> R.string.pref_key_browser_feature_autoplay_inaudible
PERSISTENT_STORAGE -> R.string.pref_key_browser_feature_persistent_storage

@ -6,6 +6,7 @@ package org.mozilla.fenix.settings.quicksettings
import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.core.net.toUri
import androidx.navigation.NavController
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -39,6 +40,13 @@ interface QuickSettingsController {
*/
fun handlePermissionToggled(permission: WebsitePermission)
/**
* Handles change a [WebsitePermission.Autoplay].
*
* @param autoplayValue [AutoplayValue] needing to be changed.
*/
fun handleAutoplayChanged(autoplayValue: AutoplayValue)
/**
* Handles a certain set of Android permissions being explicitly granted by the user.
*
@ -72,8 +80,10 @@ class DefaultQuickSettingsController(
private val quickSettingsStore: QuickSettingsFragmentStore,
private val ioScope: CoroutineScope,
private val navController: NavController,
private val session: Session?,
private var sitePermissions: SitePermissions?,
@VisibleForTesting
internal val session: Session?,
@VisibleForTesting
internal var sitePermissions: SitePermissions?,
private val settings: Settings,
private val permissionStorage: PermissionStorage,
private val reload: ReloadUrlUseCase,
@ -122,6 +132,27 @@ class DefaultQuickSettingsController(
)
}
override fun handleAutoplayChanged(autoplayValue: AutoplayValue) {
val permissions = sitePermissions
sitePermissions = if (permissions == null) {
val origin = requireNotNull(session?.url?.toUri()?.host) {
"An origin is required to change a autoplay settings from the door hanger"
}
val sitePermissions =
autoplayValue.createSitePermissionsFromCustomRules(origin, settings)
handleAutoplayAdd(sitePermissions)
sitePermissions
} else {
val newPermission = autoplayValue.updateSitePermissions(permissions)
handlePermissionsChange(autoplayValue.updateSitePermissions(newPermission))
newPermission
}
quickSettingsStore.dispatch(
WebsitePermissionAction.ChangeAutoplay(autoplayValue)
)
}
/**
* Request a certain set of runtime Android permissions.
*
@ -148,6 +179,14 @@ class DefaultQuickSettingsController(
}
}
@VisibleForTesting
internal fun handleAutoplayAdd(sitePermissions: SitePermissions) {
ioScope.launch {
permissionStorage.add(sitePermissions)
reload(session)
}
}
/**
* Navigate to toggle [SitePermissions] for the specified [PhoneFeature]
*

@ -20,7 +20,7 @@ sealed class WebsiteInfoAction : QuickSettingsFragmentAction()
/**
* All possible [WebsitePermissionsState] changes as result of user / system interactions.
*/
sealed class WebsitePermissionAction : QuickSettingsFragmentAction() {
sealed class WebsitePermissionAction(open val updatedFeature: PhoneFeature) : QuickSettingsFragmentAction() {
/**
* Change resulting from toggling a specific [WebsitePermission] for the current website.
*
@ -31,8 +31,18 @@ sealed class WebsitePermissionAction : QuickSettingsFragmentAction() {
* @param updatedEnabledStatus [Boolean] the new [WebsitePermission#enabled] which will be shown to the user.
*/
class TogglePermission(
val updatedFeature: PhoneFeature,
override val updatedFeature: PhoneFeature,
val updatedStatus: String,
val updatedEnabledStatus: Boolean
) : WebsitePermissionAction()
) : WebsitePermissionAction(updatedFeature)
/**
* Change resulting from changing a specific [WebsitePermission.Autoplay] for the current website.
*
* @param autoplayValue [AutoplayValue] backing a certain [WebsitePermission.Autoplay].
* Allows to easily identify which permission changed
*/
class ChangeAutoplay(
val autoplayValue: AutoplayValue
) : WebsitePermissionAction(PhoneFeature.AUTOPLAY)
}

@ -35,16 +35,26 @@ object WebsitePermissionsStateReducer {
state: WebsitePermissionsState,
action: WebsitePermissionAction
): WebsitePermissionsState {
val key = action.updatedFeature
val value = state.getValue(key)
return when (action) {
is WebsitePermissionAction.TogglePermission -> {
val key = action.updatedFeature
val newWebsitePermission = state.getValue(key).copy(
val toggleable = value as WebsitePermission.Toggleable
val newWebsitePermission = toggleable.copy(
status = action.updatedStatus,
isEnabled = action.updatedEnabledStatus
)
state + Pair(key, newWebsitePermission)
}
is WebsitePermissionAction.ChangeAutoplay -> {
val autoplay = value as WebsitePermission.Autoplay
val newWebsitePermission = autoplay.copy(
autoplayValue = action.autoplayValue
)
state + Pair(key, newWebsitePermission)
}
}
}
}

@ -4,12 +4,18 @@
package org.mozilla.fenix.settings.quicksettings
import android.content.Context
import androidx.annotation.ColorRes
import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.AutoplayStatus
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
import mozilla.components.lib.state.State
import org.mozilla.fenix.R
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.utils.Settings
/**
* [State] containing all data displayed to the user by this Fragment.
@ -70,10 +76,192 @@ typealias WebsitePermissionsState = Map<PhoneFeature, WebsitePermission>
* @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
)
sealed class WebsitePermission(
open val phoneFeature: PhoneFeature,
open val status: String,
open val isVisible: Boolean,
open val isEnabled: Boolean,
open val isBlockedByAndroid: Boolean
) {
data class Autoplay(
val autoplayValue: AutoplayValue,
val options: List<AutoplayValue>,
override val isVisible: Boolean
) : WebsitePermission(
PhoneFeature.AUTOPLAY,
autoplayValue.label,
isVisible,
autoplayValue.isEnabled,
isBlockedByAndroid = false
)
data class Toggleable(
override val phoneFeature: PhoneFeature,
override val status: String,
override val isVisible: Boolean,
override val isEnabled: Boolean,
override val isBlockedByAndroid: Boolean
) : WebsitePermission(
phoneFeature,
status,
isVisible,
isEnabled,
isBlockedByAndroid
)
}
sealed class AutoplayValue(
open val label: String,
open val rules: SitePermissionsRules,
open val sitePermission: SitePermissions?
) {
override fun toString() = label
abstract fun isSelected(): Boolean
abstract fun createSitePermissionsFromCustomRules(origin: String, settings: Settings): SitePermissions
abstract fun updateSitePermissions(sitePermissions: SitePermissions): SitePermissions
abstract val isEnabled: Boolean
val isVisible: Boolean get() = isSelected()
data class AllowAll(
override val label: String,
override val rules: SitePermissionsRules,
override val sitePermission: SitePermissions?
) : AutoplayValue(label, rules, sitePermission) {
override val isEnabled: Boolean = true
override fun toString() = super.toString()
override fun isSelected(): Boolean {
val actions = if (sitePermission !== null) {
listOf(
sitePermission.autoplayAudible,
sitePermission.autoplayInaudible
)
} else {
listOf(rules.autoplayAudible.toAutoplayStatus(), rules.autoplayInaudible.toAutoplayStatus())
}
return actions.all { it == AutoplayStatus.ALLOWED }
}
override fun createSitePermissionsFromCustomRules(origin: String, settings: Settings): SitePermissions {
val rules = settings.getSitePermissionsCustomSettingsRules()
return rules.copy(
autoplayAudible = AutoplayAction.ALLOWED,
autoplayInaudible = AutoplayAction.ALLOWED
).toSitePermissions(origin)
}
override fun updateSitePermissions(sitePermissions: SitePermissions): SitePermissions {
return sitePermissions.copy(
autoplayAudible = AutoplayStatus.ALLOWED,
autoplayInaudible = AutoplayStatus.ALLOWED
)
}
}
data class BlockAll(
override val label: String,
override val rules: SitePermissionsRules,
override val sitePermission: SitePermissions?
) : AutoplayValue(label, rules, sitePermission) {
override val isEnabled: Boolean = false
override fun toString() = super.toString()
override fun isSelected(): Boolean {
val actions = if (sitePermission !== null) {
listOf(
sitePermission.autoplayAudible,
sitePermission.autoplayInaudible
)
} else {
listOf(rules.autoplayAudible.toAutoplayStatus(), rules.autoplayInaudible.toAutoplayStatus())
}
return actions.all { it == AutoplayStatus.BLOCKED }
}
override fun createSitePermissionsFromCustomRules(origin: String, settings: Settings): SitePermissions {
val rules = settings.getSitePermissionsCustomSettingsRules()
return rules.copy(
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED
).toSitePermissions(origin)
}
override fun updateSitePermissions(sitePermissions: SitePermissions): SitePermissions {
return sitePermissions.copy(
autoplayAudible = AutoplayStatus.BLOCKED,
autoplayInaudible = AutoplayStatus.BLOCKED
)
}
}
data class BlockAudible(
override val label: String,
override val rules: SitePermissionsRules,
override val sitePermission: SitePermissions?
) : AutoplayValue(label, rules, sitePermission) {
override val isEnabled: Boolean = false
override fun toString() = super.toString()
override fun isSelected(): Boolean {
val (audible, inaudible) = if (sitePermission !== null) {
sitePermission.autoplayAudible to sitePermission.autoplayInaudible
} else {
rules.autoplayAudible.toAutoplayStatus() to rules.autoplayInaudible.toAutoplayStatus()
}
return audible == AutoplayStatus.BLOCKED && inaudible == AutoplayStatus.ALLOWED
}
override fun createSitePermissionsFromCustomRules(origin: String, settings: Settings): SitePermissions {
val rules = settings.getSitePermissionsCustomSettingsRules()
return rules.copy(autoplayAudible = AutoplayAction.BLOCKED, autoplayInaudible = AutoplayAction.ALLOWED)
.toSitePermissions(origin)
}
override fun updateSitePermissions(sitePermissions: SitePermissions): SitePermissions {
return sitePermissions.copy(
autoplayInaudible = AutoplayStatus.ALLOWED,
autoplayAudible = AutoplayStatus.BLOCKED
)
}
}
companion object {
fun values(
context: Context,
settings: Settings,
sitePermission: SitePermissions?
): List<AutoplayValue> {
val rules = settings.getSitePermissionsCustomSettingsRules()
return listOf(
AllowAll(
context.getString(R.string.preference_option_autoplay_allowed2),
rules,
sitePermission
),
BlockAll(
context.getString(R.string.preference_option_autoplay_blocked3),
rules,
sitePermission
),
BlockAudible(
context.getString(R.string.preference_option_autoplay_block_audio2),
rules,
sitePermission
)
)
}
fun getFallbackValue(
context: Context,
settings: Settings,
sitePermission: SitePermissions?
): AutoplayValue {
val rules = settings.getSitePermissionsCustomSettingsRules()
return BlockAudible(
context.getString(R.string.preference_option_autoplay_block_audio2),
rules,
sitePermission
)
}
}
}

@ -6,6 +6,7 @@ package org.mozilla.fenix.settings.quicksettings
import android.content.Context
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.state.state.content.PermissionHighlightsState
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.lib.state.Action
import mozilla.components.lib.state.Reducer
@ -57,13 +58,20 @@ class QuickSettingsFragmentStore(
certificateName: String,
isSecured: Boolean,
permissions: SitePermissions?,
permissionHighlights: PermissionHighlightsState,
settings: Settings
) = QuickSettingsFragmentStore(
QuickSettingsFragmentState(
webInfoState = createWebsiteInfoState(websiteUrl, websiteTitle, isSecured, certificateName),
webInfoState = createWebsiteInfoState(
websiteUrl,
websiteTitle,
isSecured,
certificateName
),
websitePermissionsState = createWebsitePermissionState(
context,
permissions,
permissionHighlights,
settings
)
)
@ -90,7 +98,8 @@ class QuickSettingsFragmentStore(
}
/**
* Construct an initial [WebsitePermissionsState] to be rendered by [WebsitePermissionsView]
* Construct an initial [WebsitePermissions
* State] to be rendered by [WebsitePermissionsView]
* containing the permissions requested by the current website.
*
* Users can modify the returned [WebsitePermissionsState] after it is initially displayed.
@ -103,11 +112,17 @@ class QuickSettingsFragmentStore(
fun createWebsitePermissionState(
context: Context,
permissions: SitePermissions?,
permissionHighlights: PermissionHighlightsState,
settings: Settings
): WebsitePermissionsState {
val state = EnumMap<PhoneFeature, WebsitePermission>(PhoneFeature::class.java)
for (feature in PhoneFeature.values()) {
state[feature] = feature.toWebsitePermission(context, permissions, settings)
state[feature] = feature.toWebsitePermission(
context,
permissions,
permissionHighlights,
settings
)
}
return state
}
@ -119,15 +134,31 @@ class QuickSettingsFragmentStore(
fun PhoneFeature.toWebsitePermission(
context: Context,
permissions: SitePermissions?,
permissionHighlights: PermissionHighlightsState,
settings: Settings
): WebsitePermission {
return WebsitePermission(
phoneFeature = this,
status = getActionLabel(context, permissions, settings),
isVisible = shouldBeVisible(permissions, settings),
isEnabled = shouldBeEnabled(context, permissions, settings),
isBlockedByAndroid = !isAndroidPermissionGranted(context)
)
return if (this == PhoneFeature.AUTOPLAY) {
val autoplayValues = AutoplayValue.values(context, settings, permissions)
val selected =
autoplayValues.firstOrNull { it.isSelected() } ?: AutoplayValue.getFallbackValue(
context,
settings,
permissions
)
WebsitePermission.Autoplay(
autoplayValue = selected,
options = autoplayValues,
isVisible = permissionHighlights.isAutoPlayBlocking || permissions !== null
)
} else {
WebsitePermission.Toggleable(
phoneFeature = this,
status = getActionLabel(context, permissions, settings),
isVisible = shouldBeVisible(permissions, settings),
isEnabled = shouldBeEnabled(context, permissions, settings),
isBlockedByAndroid = !isAndroidPermissionGranted(context)
)
}
}
}
}

@ -23,4 +23,8 @@ class QuickSettingsInteractor(
override fun onPermissionToggled(permissionState: WebsitePermission) {
controller.handlePermissionToggled(permissionState)
}
override fun onAutoplayChanged(value: AutoplayValue) {
controller.handleAutoplayChanged(value)
}
}

@ -63,7 +63,6 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
val context = requireContext()
val components = context.components
val rootView = inflateRootView(container)
quickSettingsStore = QuickSettingsFragmentStore.createStore(
context = context,
websiteUrl = args.url,
@ -71,7 +70,8 @@ class QuickSettingsSheetDialogFragment : AppCompatDialogFragment() {
isSecured = args.isSecured,
permissions = args.sitePermissions,
settings = components.settings,
certificateName = args.certificateName
certificateName = args.certificateName,
permissionHighlights = args.permissionHighlights
)
quickSettingsController = DefaultQuickSettingsController(

@ -7,12 +7,25 @@ package org.mozilla.fenix.settings.quicksettings
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.TextView
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.AppCompatSpinner
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 org.mozilla.fenix.settings.PhoneFeature.AUTOPLAY
import org.mozilla.fenix.settings.PhoneFeature.CAMERA
import org.mozilla.fenix.settings.PhoneFeature.MICROPHONE
import org.mozilla.fenix.settings.PhoneFeature.LOCATION
import org.mozilla.fenix.settings.PhoneFeature.NOTIFICATION
import org.mozilla.fenix.settings.PhoneFeature.PERSISTENT_STORAGE
import org.mozilla.fenix.settings.PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS
import org.mozilla.fenix.settings.quicksettings.WebsitePermissionsView.PermissionViewHolder.SpinnerPermission
import org.mozilla.fenix.settings.quicksettings.WebsitePermissionsView.PermissionViewHolder.ToggleablePermission
import java.util.EnumMap
/**
@ -31,6 +44,13 @@ interface WebsitePermissionInteractor {
* @param permissionState current [WebsitePermission] that the user wants toggled.
*/
fun onPermissionToggled(permissionState: WebsitePermission)
/**
* Indicates the user changed the status of a an autoplay permission.
*
* @param value current [AutoplayValue] that the user wants change.
*/
fun onAutoplayChanged(value: AutoplayValue)
}
/**
@ -52,25 +72,30 @@ class WebsitePermissionsView(
val view: View = LayoutInflater.from(context)
.inflate(R.layout.quicksettings_permissions, containerView, true)
private val permissionViews: Map<PhoneFeature, PermissionViewHolder> = EnumMap(
@VisibleForTesting
internal var 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(
CAMERA to ToggleablePermission(view.cameraLabel, view.cameraStatus),
LOCATION to ToggleablePermission(view.locationLabel, view.locationStatus),
MICROPHONE to ToggleablePermission(
view.microphoneLabel,
view.microphoneStatus
),
PhoneFeature.NOTIFICATION to PermissionViewHolder(
NOTIFICATION to ToggleablePermission(
view.notificationLabel,
view.notificationStatus
),
PhoneFeature.PERSISTENT_STORAGE to PermissionViewHolder(
PERSISTENT_STORAGE to ToggleablePermission(
view.persistentStorageLabel,
view.persistentStorageStatus
),
PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS to PermissionViewHolder(
MEDIA_KEY_SYSTEM_ACCESS to ToggleablePermission(
view.mediaKeySystemAccessLabel,
view.mediaKeySystemAccessStatus
),
AUTOPLAY to SpinnerPermission(
view.autoplayLabel,
view.autoplayStatus
)
)
)
@ -102,13 +127,68 @@ class WebsitePermissionsView(
* @param permissionState [WebsitePermission] specific permission that can be shown to the user.
* @param viewHolder Views that will render [WebsitePermission]'s state.
*/
private fun bindPermission(permissionState: WebsitePermission, viewHolder: PermissionViewHolder) {
@VisibleForTesting
internal 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) }
when (viewHolder) {
is ToggleablePermission -> {
viewHolder.status.text = permissionState.status
viewHolder.status.setOnClickListener {
interactor.onPermissionToggled(
permissionState
)
}
}
is SpinnerPermission -> {
if (permissionState !is WebsitePermission.Autoplay) {
throw IllegalArgumentException("${permissionState.phoneFeature} is not supported")
}
val selectedIndex = permissionState.options.indexOf(permissionState.autoplayValue)
val adapter = ArrayAdapter(
context,
R.layout.quicksettings_permission_spinner_item,
permissionState.options
)
adapter.setDropDownViewResource(R.layout.quicksetting_permission_spinner_dropdown)
viewHolder.status.adapter = adapter
viewHolder.status.setSelection(selectedIndex, false)
viewHolder.status.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val type = viewHolder.status.selectedItem as AutoplayValue
interactor.onAutoplayChanged(type)
}
override fun onNothingSelected(parent: AdapterView<*>?) = Unit
}
}
}
}
data class PermissionViewHolder(val label: TextView, val status: TextView)
sealed class PermissionViewHolder(open val label: TextView, open val status: View) {
data class ToggleablePermission(
override val label: TextView,
override val status: TextView
) :
PermissionViewHolder(label, status)
data class SpinnerPermission(
override val label: TextView,
override val status: AppCompatSpinner
) :
PermissionViewHolder(label, status)
}
}

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/enabled"
android:drawable="@drawable/ic_autoplay_enabled"
android:state_enabled="true" />
<item
android:id="@+id/disabled"
android:drawable="@drawable/ic_autoplay_disabled" />
</selector>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/disabled_text"
android:pathData="M19.168,8.458A7.985,7.985 0,0 1,8.462 19.164L6.987,20.639A9.986,9.986 0,0 0,20.643 6.984Z" />
<path
android:fillColor="@color/disabled_text"
android:pathData="M21.707,2.293a1,1 0,0 0,-1.414 0l-1.97,1.97a9.915,9.915 0,0 0,-2.991 -1.694,1 1,0 1,0 -0.666,1.885 7.88,7.88 0,0 1,2.227 1.239L13.311,9.275 10.5,7.643A1,1 0,0 0,9 8.508v5.078l-3.3,3.3A7.922,7.922 0,0 1,4 12,8 8,0 0,1 7,5.773V9.5a0.5,0.5 0,0 0,1 0v-6A0.5,0.5 0,0 0,7.5 3h-5a0.5,0.5 0,0 0,0 1H6.024A9.939,9.939 0,0 0,4.273 18.313l-1.98,1.98a1,1 0,1 0,1.414 1.414l18,-18A1,1 0,0 0,21.707 2.293Z" />
<path
android:fillColor="@color/disabled_text"
android:pathData="M16.5,11.128l-4.166,4.165 4.179,-2.428a1,1 0,0 0,0 -1.73Z" />
</vector>

@ -2,12 +2,13 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?primaryText"
android:pathData="M4.47 7.5l3-3c0.3-0.3 0.7-0.3 1 0l4.2 4.2c0.8-1 0.7-2.4-0.2-3.3L9.84 2.7a2.5 2.5 0 0 0-3.53 0L2.77 6.28a2.5 2.5 0 0 0 0 3.52l2.66 2.67c0.9 0.9 2.25 1 3.2 0.2l-4.2-4.2a0.7 0.7 0 0 1 0-1zm16.8 6.63a2.5 2.5 0 0 1 0 3.53l-3.55 3.54a2.5 2.5 0 0 1-3.53 0l-2.7-2.7a2.49 2.49 0 0 1-0.2-3.24l4.2 4.2a0.7 0.7 0 0 0 1 0l3.04-3.05a0.7 0.7 0 0 0 0-1l-4.2-4.2c1-0.8 2.4-0.7 3.3 0.2l2.66 2.66zm-5.1 0.56a1 1 0 0 1-1.42 1.41L7.8 9.2A1 1 0 1 1 9.24 7.8l6.9 6.9z" />
</vector>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/enabled"
android:drawable="@drawable/ic_link_enabled"
android:state_enabled="true" />
<item
android:id="@+id/disabled"
android:drawable="@drawable/ic_link_disabled" />
</selector>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@color/disabled_text"
android:pathData="M4.47 7.5l3-3c0.3-0.3 0.7-0.3 1 0l4.2 4.2c0.8-1 0.7-2.4-0.2-3.3L9.84 2.7a2.5 2.5 0 0 0-3.53 0L2.77 6.28a2.5 2.5 0 0 0 0 3.52l2.66 2.67c0.9 0.9 2.25 1 3.2 0.2l-4.2-4.2a0.7 0.7 0 0 1 0-1zm16.8 6.63a2.5 2.5 0 0 1 0 3.53l-3.55 3.54a2.5 2.5 0 0 1-3.53 0l-2.7-2.7a2.49 2.49 0 0 1-0.2-3.24l4.2 4.2a0.7 0.7 0 0 0 1 0l3.04-3.05a0.7 0.7 0 0 0 0-1l-4.2-4.2c1-0.8 2.4-0.7 3.3 0.2l2.66 2.66zm-5.1 0.56a1 1 0 0 1-1.42 1.41L7.8 9.2A1 1 0 1 1 9.24 7.8l6.9 6.9z" />
</vector>

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?primaryText"
android:pathData="M4.47 7.5l3-3c0.3-0.3 0.7-0.3 1 0l4.2 4.2c0.8-1 0.7-2.4-0.2-3.3L9.84 2.7a2.5 2.5 0 0 0-3.53 0L2.77 6.28a2.5 2.5 0 0 0 0 3.52l2.66 2.67c0.9 0.9 2.25 1 3.2 0.2l-4.2-4.2a0.7 0.7 0 0 1 0-1zm16.8 6.63a2.5 2.5 0 0 1 0 3.53l-3.55 3.54a2.5 2.5 0 0 1-3.53 0l-2.7-2.7a2.49 2.49 0 0 1-0.2-3.24l4.2 4.2a0.7 0.7 0 0 0 1 0l3.04-3.05a0.7 0.7 0 0 0 0-1l-4.2-4.2c1-0.8 2.4-0.7 3.3 0.2l2.66 2.66zm-5.1 0.56a1 1 0 0 1-1.42 1.41L7.8 9.2A1 1 0 1 1 9.24 7.8l6.9 6.9z" />
</vector>

@ -1,13 +1,7 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:autoMirrored="true">
<path
android:fillColor="?primaryText"
android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
</vector>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/enabled" android:drawable="@drawable/ic_storage_enabled" android:state_enabled="true" />
<item android:id="@+id/disabled" android:drawable="@drawable/ic_storage_disabled" />
</selector>

@ -0,0 +1,13 @@
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:autoMirrored="true">
<path
android:fillColor="@color/disabled_text"
android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
</vector>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:autoMirrored="true">
<path
android:fillColor="?primaryText"
android:pathData="M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z"/>
</vector>

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<CheckedTextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@android:id/text1"
style="?android:attr/spinnerDropDownItemStyle"
android:layout_width="match_parent"
android:layout_height="48dp"
android:background="@drawable/etp_spinner_item_background"
android:gravity="end|center_vertical"
android:paddingStart="15dp"
android:paddingEnd="15dp"
tools:text="Allow Audible"
android:textColor="?accentUsedOnDarkBackground" />

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="@dimen/quicksettings_item_height"
android:gravity="end|center_vertical"
android:paddingStart="0dp"
android:paddingEnd="12dp"
android:textColor="?accentUsedOnDarkBackground"
tools:text="Allowed" />

@ -145,7 +145,7 @@
app:drawableStartCompat="@drawable/ic_link"
android:text="@string/preference_phone_feature_media_key_system_access"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/autoplayLabel"
app:layout_constraintEnd_toStartOf="@id/mediaKeySystemAccessStatus"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
@ -156,10 +156,39 @@
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@id/autoplayStatus"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/mediaKeySystemAccessLabel"
tools:text="@string/preference_option_phone_feature_blocked"
tools:visibility="visible" />
<TextView
android:id="@+id/autoplayLabel"
style="@style/QuickSettingsText.Icon"
android:layout_width="0dp"
android:layout_height="@dimen/quicksettings_item_height"
app:drawableStartCompat="@drawable/ic_autoplay"
android:text="@string/preference_browser_feature_autoplay"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/mediaKeySystemAccessLabel"
app:layout_constraintEnd_toStartOf="@id/autoplayStatus"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<androidx.appcompat.widget.AppCompatSpinner
android:id="@+id/autoplayStatus"
android:layout_width="wrap_content"
android:layout_height="@dimen/quicksettings_item_height"
android:visibility="gone"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:gravity="end|center_vertical"
android:backgroundTint="?accentUsedOnDarkBackground"
app:layout_constraintBottom_toTopOf="@id/mediaKeySystemAccessStatus"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/autoplayLabel"
tools:listitem="@android:layout/simple_list_item_1"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

@ -756,6 +756,9 @@
android:name="certificateName"
android:defaultValue=" "
app:argType="string" />
<argument
android:name="permissionHighlights"
app:argType="mozilla.components.browser.state.state.content.PermissionHighlightsState"/>
</dialog>
<fragment
android:id="@+id/accountProblemFragment"

@ -0,0 +1,97 @@
/* 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.components
import androidx.paging.DataSource
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runBlockingTest
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsStorage
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(FenixRobolectricTestRunner::class)
class PermissionStorageTest {
@Test
fun `add permission`() = runBlockingTest {
val sitePermissions: SitePermissions = mockk(relaxed = true)
val sitePermissionsStorage: SitePermissionsStorage = mockk(relaxed = true)
val storage = PermissionStorage(testContext, this.coroutineContext, sitePermissionsStorage)
storage.add(sitePermissions)
verify { sitePermissionsStorage.save(sitePermissions) }
}
@Test
fun `find sitePermissions by origin`() = runBlockingTest {
val sitePermissions: SitePermissions = mockk(relaxed = true)
val sitePermissionsStorage: SitePermissionsStorage = mockk(relaxed = true)
val storage = PermissionStorage(testContext, this.coroutineContext, sitePermissionsStorage)
every { sitePermissionsStorage.findSitePermissionsBy(any()) } returns sitePermissions
val result = storage.findSitePermissionsBy("origin")
verify { sitePermissionsStorage.findSitePermissionsBy("origin") }
assertEquals(sitePermissions, result)
}
@Test
fun `update SitePermissions`() = runBlockingTest {
val sitePermissions: SitePermissions = mockk(relaxed = true)
val sitePermissionsStorage: SitePermissionsStorage = mockk(relaxed = true)
val storage = PermissionStorage(testContext, this.coroutineContext, sitePermissionsStorage)
storage.updateSitePermissions(sitePermissions)
verify { sitePermissionsStorage.update(sitePermissions) }
}
@Test
fun `get sitePermissions paged`() = runBlockingTest {
val dataSource: DataSource.Factory<Int, SitePermissions> = mockk(relaxed = true)
val sitePermissionsStorage: SitePermissionsStorage = mockk(relaxed = true)
val storage = PermissionStorage(testContext, this.coroutineContext, sitePermissionsStorage)
every { sitePermissionsStorage.getSitePermissionsPaged() } returns dataSource
val result = storage.getSitePermissionsPaged()
verify { sitePermissionsStorage.getSitePermissionsPaged() }
assertEquals(dataSource, result)
}
@Test
fun `delete sitePermissions`() = runBlockingTest {
val sitePermissions: SitePermissions = mockk(relaxed = true)
val sitePermissionsStorage: SitePermissionsStorage = mockk(relaxed = true)
val storage = PermissionStorage(testContext, this.coroutineContext, sitePermissionsStorage)
storage.deleteSitePermissions(sitePermissions)
verify { sitePermissionsStorage.remove(sitePermissions) }
}
@Test
fun `delete all sitePermissions`() = runBlockingTest {
val sitePermissionsStorage: SitePermissionsStorage = mockk(relaxed = true)
val storage = PermissionStorage(testContext, this.coroutineContext, sitePermissionsStorage)
storage.deleteAllSitePermissions()
verify { sitePermissionsStorage.removeAll() }
}
}

@ -65,6 +65,7 @@ class PhoneFeatureTest {
assertEquals("Notification", PhoneFeature.NOTIFICATION.getLabel(testContext))
assertEquals("Autoplay", PhoneFeature.AUTOPLAY_AUDIBLE.getLabel(testContext))
assertEquals("Autoplay", PhoneFeature.AUTOPLAY_INAUDIBLE.getLabel(testContext))
assertEquals("Autoplay", PhoneFeature.AUTOPLAY.getLabel(testContext))
}
@Test
@ -75,6 +76,7 @@ class PhoneFeatureTest {
assertEquals(R.string.pref_key_phone_feature_notification, PhoneFeature.NOTIFICATION.getPreferenceId())
assertEquals(R.string.pref_key_browser_feature_autoplay_audible, PhoneFeature.AUTOPLAY_AUDIBLE.getPreferenceId())
assertEquals(R.string.pref_key_browser_feature_autoplay_inaudible, PhoneFeature.AUTOPLAY_INAUDIBLE.getPreferenceId())
assertEquals(R.string.pref_key_browser_feature_autoplay_audible, PhoneFeature.AUTOPLAY.getPreferenceId())
assertEquals(
"pref_key_browser_feature_autoplay_inaudible",

@ -0,0 +1,381 @@
/* 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 io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissions.AutoplayStatus
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertTrue
import org.junit.Assert.assertFalse
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class AutoplayValueTest {
@MockK(relaxed = true)
private lateinit var settings: Settings
@Before
fun setup() {
MockKAnnotations.init(this)
}
@Test
fun `AllowAll - isSelected`() {
var rules = getRules().copy(
autoplayAudible = AutoplayAction.ALLOWED,
autoplayInaudible = AutoplayAction.ALLOWED
)
var value = AutoplayValue.AllowAll(
label = "label",
rules = rules,
sitePermission = null
)
assertTrue(value.isSelected())
rules = rules.copy(
autoplayAudible = AutoplayAction.BLOCKED
)
value = AutoplayValue.AllowAll(
label = "label",
rules = rules,
sitePermission = null
)
assertFalse(value.isSelected())
value = AutoplayValue.AllowAll(
label = "label",
rules = rules,
sitePermission = SitePermissions(
origin = "",
savedAt = 0L,
autoplayAudible = AutoplayStatus.ALLOWED,
autoplayInaudible = AutoplayStatus.ALLOWED
)
)
assertTrue(value.isSelected())
value = AutoplayValue.AllowAll(
label = "label",
rules = rules,
sitePermission = SitePermissions(
origin = "",
savedAt = 0L,
autoplayAudible = AutoplayStatus.BLOCKED,
autoplayInaudible = AutoplayStatus.BLOCKED
)
)
assertFalse(value.isSelected())
}
@Test
fun `BlockAll - isSelected`() {
var rules = getRules().copy(
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED
)
var value = AutoplayValue.BlockAll(
label = "label",
rules = rules,
sitePermission = null
)
assertTrue(value.isSelected())
rules = rules.copy(
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.ALLOWED
)
value = AutoplayValue.BlockAll(
label = "label",
rules = rules,
sitePermission = null
)
assertFalse(value.isSelected())
value = AutoplayValue.BlockAll(
label = "label",
rules = rules,
sitePermission = SitePermissions(
origin = "",
savedAt = 0L,
autoplayAudible = AutoplayStatus.BLOCKED,
autoplayInaudible = AutoplayStatus.BLOCKED
)
)
assertTrue(value.isSelected())
value = AutoplayValue.BlockAll(
label = "label",
rules = rules,
sitePermission = SitePermissions(
origin = "",
savedAt = 0L,
autoplayAudible = AutoplayStatus.ALLOWED,
autoplayInaudible = AutoplayStatus.BLOCKED
)
)
assertFalse(value.isSelected())
}
@Test
fun `BlockAudible - isSelected`() {
var rules = getRules().copy(
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.ALLOWED
)
var value = AutoplayValue.BlockAudible(
label = "label",
rules = rules,
sitePermission = null
)
assertTrue(value.isSelected())
rules = rules.copy(
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED
)
value = AutoplayValue.BlockAudible(
label = "label",
rules = rules,
sitePermission = null
)
assertFalse(value.isSelected())
value = AutoplayValue.BlockAudible(
label = "label",
rules = rules,
sitePermission = SitePermissions(
origin = "",
savedAt = 0L,
autoplayAudible = AutoplayStatus.BLOCKED,
autoplayInaudible = AutoplayStatus.ALLOWED
)
)
assertTrue(value.isSelected())
value = AutoplayValue.BlockAudible(
label = "label",
rules = rules,
sitePermission = SitePermissions(
origin = "",
savedAt = 0L,
autoplayAudible = AutoplayStatus.ALLOWED,
autoplayInaudible = AutoplayStatus.ALLOWED
)
)
assertFalse(value.isSelected())
}
@Test
fun `AllowAll - createSitePermissionsFromCustomRules`() {
val rules = getRules().copy(
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED
)
every { settings.getSitePermissionsCustomSettingsRules() } returns rules
val value = AutoplayValue.AllowAll(
label = "label",
rules = rules,
sitePermission = null
)
val result = value.createSitePermissionsFromCustomRules("mozilla.org", settings)
assertEquals(AutoplayStatus.ALLOWED, result.autoplayAudible)
assertEquals(AutoplayStatus.ALLOWED, result.autoplayInaudible)
assertEquals(rules.camera.toStatus(), result.camera)
assertEquals(rules.location.toStatus(), result.location)
assertEquals(rules.microphone.toStatus(), result.microphone)
assertEquals(rules.notification.toStatus(), result.notification)
assertEquals(rules.persistentStorage.toStatus(), result.localStorage)
assertEquals(rules.mediaKeySystemAccess.toStatus(), result.mediaKeySystemAccess)
}
@Test
fun `BlockAll - createSitePermissionsFromCustomRules`() {
val rules = getRules().copy(
autoplayAudible = AutoplayAction.ALLOWED,
autoplayInaudible = AutoplayAction.ALLOWED
)
every { settings.getSitePermissionsCustomSettingsRules() } returns rules
val value = AutoplayValue.BlockAll(
label = "label",
rules = rules,
sitePermission = null
)
val result = value.createSitePermissionsFromCustomRules("mozilla.org", settings)
assertEquals(AutoplayStatus.BLOCKED, result.autoplayAudible)
assertEquals(AutoplayStatus.BLOCKED, result.autoplayInaudible)
assertEquals(rules.camera.toStatus(), result.camera)
assertEquals(rules.location.toStatus(), result.location)
assertEquals(rules.microphone.toStatus(), result.microphone)
assertEquals(rules.notification.toStatus(), result.notification)
assertEquals(rules.persistentStorage.toStatus(), result.localStorage)
assertEquals(rules.mediaKeySystemAccess.toStatus(), result.mediaKeySystemAccess)
}
@Test
fun `BlockAudible - createSitePermissionsFromCustomRules`() {
val rules = getRules().copy(
autoplayAudible = AutoplayAction.ALLOWED,
autoplayInaudible = AutoplayAction.ALLOWED
)
every { settings.getSitePermissionsCustomSettingsRules() } returns rules
val value = AutoplayValue.BlockAudible(
label = "label",
rules = rules,
sitePermission = null
)
val result = value.createSitePermissionsFromCustomRules("mozilla.org", settings)
assertEquals(AutoplayStatus.BLOCKED, result.autoplayAudible)
assertEquals(AutoplayStatus.ALLOWED, result.autoplayInaudible)
assertEquals(rules.camera.toStatus(), result.camera)
assertEquals(rules.location.toStatus(), result.location)
assertEquals(rules.microphone.toStatus(), result.microphone)
assertEquals(rules.notification.toStatus(), result.notification)
assertEquals(rules.persistentStorage.toStatus(), result.localStorage)
assertEquals(rules.mediaKeySystemAccess.toStatus(), result.mediaKeySystemAccess)
}
@Test
fun `AllowAll - updateSitePermissions`() {
val sitePermissions = SitePermissions(
origin = "origin",
savedAt = 0L,
autoplayAudible = AutoplayStatus.BLOCKED,
autoplayInaudible = AutoplayStatus.BLOCKED
)
val value = AutoplayValue.AllowAll(
label = "label",
rules = mock(),
sitePermission = null
)
val result = value.updateSitePermissions(sitePermissions)
assertEquals(AutoplayStatus.ALLOWED, result.autoplayAudible)
assertEquals(AutoplayStatus.ALLOWED, result.autoplayInaudible)
assertEquals(sitePermissions.camera, result.camera)
assertEquals(sitePermissions.location, result.location)
assertEquals(sitePermissions.microphone, result.microphone)
assertEquals(sitePermissions.notification, result.notification)
assertEquals(sitePermissions.localStorage, result.localStorage)
assertEquals(sitePermissions.mediaKeySystemAccess, result.mediaKeySystemAccess)
}
@Test
fun `BlockAll - updateSitePermissions`() {
val sitePermissions = SitePermissions(
origin = "origin",
savedAt = 0L,
autoplayAudible = AutoplayStatus.ALLOWED,
autoplayInaudible = AutoplayStatus.ALLOWED
)
val value = AutoplayValue.BlockAll(
label = "label",
rules = mock(),
sitePermission = null
)
val result = value.updateSitePermissions(sitePermissions)
assertEquals(AutoplayStatus.BLOCKED, result.autoplayAudible)
assertEquals(AutoplayStatus.BLOCKED, result.autoplayInaudible)
assertEquals(sitePermissions.camera, result.camera)
assertEquals(sitePermissions.location, result.location)
assertEquals(sitePermissions.microphone, result.microphone)
assertEquals(sitePermissions.notification, result.notification)
assertEquals(sitePermissions.localStorage, result.localStorage)
assertEquals(sitePermissions.mediaKeySystemAccess, result.mediaKeySystemAccess)
}
@Test
fun `BlockAudible - updateSitePermissions`() {
val sitePermissions = SitePermissions(
origin = "origin",
savedAt = 0L,
autoplayAudible = AutoplayStatus.ALLOWED,
autoplayInaudible = AutoplayStatus.BLOCKED
)
val value = AutoplayValue.BlockAudible(
label = "label",
rules = mock(),
sitePermission = null
)
val result = value.updateSitePermissions(sitePermissions)
assertEquals(AutoplayStatus.BLOCKED, result.autoplayAudible)
assertEquals(AutoplayStatus.ALLOWED, result.autoplayInaudible)
assertEquals(sitePermissions.camera, result.camera)
assertEquals(sitePermissions.location, result.location)
assertEquals(sitePermissions.microphone, result.microphone)
assertEquals(sitePermissions.notification, result.notification)
assertEquals(sitePermissions.localStorage, result.localStorage)
assertEquals(sitePermissions.mediaKeySystemAccess, result.mediaKeySystemAccess)
}
@Test
fun `values - contains the right values`() {
val values = AutoplayValue.values(testContext, settings, null)
assertTrue(values.any { it is AutoplayValue.AllowAll })
assertTrue(values.any { it is AutoplayValue.BlockAll })
assertTrue(values.any { it is AutoplayValue.BlockAudible })
}
private fun getRules() = SitePermissionsRules(
camera = Action.ASK_TO_ALLOW,
location = Action.ASK_TO_ALLOW,
microphone = Action.ASK_TO_ALLOW,
notification = Action.ASK_TO_ALLOW,
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED,
persistentStorage = Action.ASK_TO_ALLOW,
mediaKeySystemAccess = Action.ASK_TO_ALLOW
)
}

@ -13,6 +13,8 @@ import io.mockk.just
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import io.mockk.MockKAnnotations
import io.mockk.impl.annotations.MockK
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScope
import kotlinx.coroutines.test.runBlockingTest
@ -21,10 +23,12 @@ 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.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertSame
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.PermissionStorage
@ -39,33 +43,64 @@ import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class DefaultQuickSettingsControllerTest {
private val context = testContext
private val store = mockk<QuickSettingsFragmentStore>()
@MockK
private lateinit var store: QuickSettingsFragmentStore
private val coroutinesScope = TestCoroutineScope()
private val navController = mockk<NavController>(relaxed = true)
private val browserSession = mockk<Session>()
private val sitePermissions: SitePermissions = SitePermissions(origin = "", savedAt = 123)
private val appSettings = mockk<Settings>(relaxed = true)
private val permissionStorage = mockk<PermissionStorage>(relaxed = true)
private val reload = mockk<SessionUseCases.ReloadUrlUseCase>(relaxed = true)
private val addNewTab = mockk<TabsUseCases.AddNewTabUseCase>(relaxed = true)
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 = spyk(DefaultQuickSettingsController(
context = context,
quickSettingsStore = store,
ioScope = coroutinesScope,
navController = navController,
session = browserSession,
sitePermissions = sitePermissions,
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
))
@MockK(relaxed = true)
private lateinit var navController: NavController
@MockK(relaxed = true)
private lateinit var browserSession: Session
private lateinit var sitePermissions: SitePermissions
@MockK(relaxed = true)
private lateinit var appSettings: Settings
@MockK(relaxed = true)
private lateinit var permissionStorage: PermissionStorage
@MockK(relaxed = true)
private lateinit var reload: SessionUseCases.ReloadUrlUseCase
@MockK(relaxed = true)
private lateinit var addNewTab: TabsUseCases.AddNewTabUseCase
@MockK(relaxed = true)
private lateinit var requestPermissions: (Array<String>) -> Unit
@MockK(relaxed = true)
private lateinit var displayPermissions: () -> Unit
@MockK(relaxed = true)
private lateinit var dismiss: () -> Unit
private lateinit var controller: DefaultQuickSettingsController
@Before
fun setUp() {
MockKAnnotations.init(this)
sitePermissions = SitePermissions(origin = "", savedAt = 123)
controller = spyk(
DefaultQuickSettingsController(
context = context,
quickSettingsStore = store,
ioScope = coroutinesScope,
navController = navController,
session = browserSession,
sitePermissions = sitePermissions,
settings = appSettings,
permissionStorage = permissionStorage,
reload = reload,
addNewTab = addNewTab,
requestRuntimePermissions = requestPermissions,
displayPermissions = displayPermissions,
dismiss = dismiss
)
)
}
@After
fun cleanUp() {
@ -149,6 +184,41 @@ class DefaultQuickSettingsControllerTest {
}
}
@Test
fun `handleAutoplayChanged will add autoplay permission`() {
val autoplayValue = mockk<AutoplayValue.AllowAll>(relaxed = true)
every { store.dispatch(any()) } returns mockk()
every { browserSession.url } returns "https://www.mozilla.org"
every { controller.handleAutoplayAdd(any()) } returns Unit
controller.sitePermissions = null
controller.handleAutoplayChanged(autoplayValue)
verify {
controller.handleAutoplayAdd(any())
store.dispatch(any())
}
}
@Test
fun `handleAutoplayChanged will update autoplay permission`() {
val autoplayValue = mockk<AutoplayValue.AllowAll>(relaxed = true)
every { store.dispatch(any()) } returns mockk()
every { browserSession.url } returns "https://www.mozilla.org"
every { controller.handleAutoplayAdd(any()) } returns Unit
every { controller.handlePermissionsChange(any()) } returns Unit
every { autoplayValue.updateSitePermissions(any()) } returns mock()
controller.handleAutoplayChanged(autoplayValue)
verify {
autoplayValue.updateSitePermissions(any())
store.dispatch(any())
}
}
@Test
fun `handleAndroidPermissionGranted should update the View's state`() {
val featureGranted = PhoneFeature.CAMERA
@ -178,7 +248,6 @@ class DefaultQuickSettingsControllerTest {
}
@Test
@ExperimentalCoroutinesApi
fun `handlePermissionsChange should store the updated permission and reload webpage`() = coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
@ -190,4 +259,17 @@ class DefaultQuickSettingsControllerTest {
reload(browserSession)
}
}
@Test
fun `handleAutoplayAdd should store the updated permission and reload webpage`() = coroutinesScope.runBlockingTest {
val testPermissions = mockk<SitePermissions>()
controller.handleAutoplayAdd(testPermissions)
advanceUntilIdle()
coVerifyOrder {
permissionStorage.add(testPermissions)
reload(browserSession)
}
}
}

@ -0,0 +1,75 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.settings.quicksettings
import mozilla.components.support.test.mock
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.PhoneFeature
@RunWith(FenixRobolectricTestRunner::class)
class QuickSettingsFragmentReducerTest {
@Test
fun `WebsitePermissionAction - TogglePermission`() {
val toggleablePermission = WebsitePermission.Toggleable(
phoneFeature = PhoneFeature.CAMERA,
status = "status",
isVisible = false,
isEnabled = false,
isBlockedByAndroid = false
)
val map =
mapOf<PhoneFeature, WebsitePermission>(PhoneFeature.CAMERA to toggleablePermission)
val state = QuickSettingsFragmentState(mock(), map)
val newState = quickSettingsFragmentReducer(
state,
WebsitePermissionAction.TogglePermission(
updatedFeature = PhoneFeature.CAMERA,
updatedStatus = "newStatus",
updatedEnabledStatus = true
)
)
val result = newState.websitePermissionsState[PhoneFeature.CAMERA]!!
assertEquals("newStatus", result.status)
assertTrue(result.isEnabled)
}
@Test
fun `WebsitePermissionAction - ChangeAutoplay`() {
val permissionPermission = WebsitePermission.Autoplay(
autoplayValue = AutoplayValue.BlockAll(
label = "label",
rules = mock(),
sitePermission = null
),
options = emptyList(),
isVisible = false
)
val map =
mapOf<PhoneFeature, WebsitePermission>(PhoneFeature.AUTOPLAY to permissionPermission)
val state = QuickSettingsFragmentState(mock(), map)
val autoplayValue = AutoplayValue.AllowAll(
label = "newLabel",
rules = mock(),
sitePermission = null
)
val newState = quickSettingsFragmentReducer(
state,
WebsitePermissionAction.ChangeAutoplay(autoplayValue)
)
val result =
newState.websitePermissionsState[PhoneFeature.AUTOPLAY] as WebsitePermission.Autoplay
assertEquals(autoplayValue, result.autoplayValue)
}
}

@ -6,12 +6,18 @@ package org.mozilla.fenix.settings.quicksettings
import android.content.pm.PackageManager
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.MockKAnnotations
import io.mockk.mockk
import io.mockk.spyk
import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import mozilla.components.browser.state.state.content.PermissionHighlightsState
import mozilla.components.feature.sitepermissions.SitePermissions
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.AutoplayAction
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@ -19,6 +25,7 @@ import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNotSame
import org.junit.Assert.assertSame
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
@ -33,16 +40,34 @@ import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class QuickSettingsFragmentStoreTest {
private val context = spyk(testContext)
private val permissions = mockk<SitePermissions>()
private val appSettings = mockk<Settings>()
@MockK(relaxed = true)
private lateinit var permissions: SitePermissions
@MockK(relaxed = true)
private lateinit var permissionHighlights: PermissionHighlightsState
@MockK(relaxed = true)
private lateinit var appSettings: Settings
@Before
fun setup() {
MockKAnnotations.init(this)
every { appSettings.getSitePermissionsCustomSettingsRules() } returns getRules()
}
@Test
fun `createStore constructs a QuickSettingsFragmentState`() {
val settings = mockk<Settings>(relaxed = true)
val permissions = mockk<SitePermissions>(relaxed = true)
val store = QuickSettingsFragmentStore.createStore(
context, "url", "Hello", "issuer", true, permissions, settings
context = context,
websiteUrl = "url",
websiteTitle = "Hello",
certificateName = "issuer",
isSecured = true,
permissions = permissions,
permissionHighlights = permissionHighlights,
settings = appSettings
)
assertNotNull(store)
@ -83,6 +108,8 @@ class QuickSettingsFragmentStoreTest {
@Test
fun `createWebsitePermissionState helps in constructing an initial WebsitePermissionState for it's Store`() {
val permissionHighlights = mockk<PermissionHighlightsState>(relaxed = true)
every {
context.checkPermission(
any(),
@ -96,12 +123,12 @@ class QuickSettingsFragmentStoreTest {
every { permissions.location } returns SitePermissions.Status.ALLOWED
every { permissions.localStorage } returns SitePermissions.Status.ALLOWED
every { permissions.mediaKeySystemAccess } returns SitePermissions.Status.NO_DECISION
every { permissions.autoplayAudible } returns SitePermissions.Status.BLOCKED
every { permissions.autoplayInaudible } returns SitePermissions.Status.BLOCKED
every { permissions.autoplayAudible } returns SitePermissions.AutoplayStatus.ALLOWED
every { permissions.autoplayInaudible } returns SitePermissions.AutoplayStatus.BLOCKED
every { appSettings.getAutoplayUserSetting(any()) } returns AUTOPLAY_BLOCK_ALL
val state = QuickSettingsFragmentStore.createWebsitePermissionState(
context, permissions, appSettings
context, permissions, permissionHighlights, appSettings
)
// Just need to know that the WebsitePermissionsState properties are initialized.
@ -115,6 +142,7 @@ class QuickSettingsFragmentStoreTest {
assertNotNull(state[PhoneFeature.AUTOPLAY_INAUDIBLE])
assertNotNull(state[PhoneFeature.PERSISTENT_STORAGE])
assertNotNull(state[PhoneFeature.MEDIA_KEY_SYSTEM_ACCESS])
assertNotNull(state[PhoneFeature.AUTOPLAY])
}
@Test
@ -129,8 +157,14 @@ class QuickSettingsFragmentStoreTest {
)
}.returns(PackageManager.PERMISSION_GRANTED)
every { permissions.camera } returns SitePermissions.Status.ALLOWED
every { permissionHighlights.isAutoPlayBlocking } returns true
val websitePermission = cameraFeature.toWebsitePermission(context, permissions, appSettings)
val websitePermission = cameraFeature.toWebsitePermission(
context = context,
permissions = permissions,
permissionHighlights = permissionHighlights,
settings = appSettings
)
assertNotNull(websitePermission)
assertEquals(cameraFeature, websitePermission.phoneFeature)
@ -138,14 +172,33 @@ class QuickSettingsFragmentStoreTest {
assertTrue(websitePermission.isVisible)
assertTrue(websitePermission.isEnabled)
assertFalse(websitePermission.isBlockedByAndroid)
val autoplayPermission = PhoneFeature.AUTOPLAY.toWebsitePermission(
context = context,
permissions = permissions,
permissionHighlights = permissionHighlights,
settings = appSettings
) as WebsitePermission.Autoplay
assertNotNull(autoplayPermission)
assertNotNull(autoplayPermission.autoplayValue)
assertEquals(PhoneFeature.AUTOPLAY, autoplayPermission.phoneFeature)
assertTrue(websitePermission.isVisible)
assertTrue(websitePermission.isEnabled)
}
@Test
fun `PhoneFeature#getPermissionStatus gets the permission properties from delegates`() {
val permissionHighlights = mockk<PermissionHighlightsState>(relaxed = true)
val phoneFeature = PhoneFeature.CAMERA
every { permissions.camera } returns SitePermissions.Status.NO_DECISION
val permissionsStatus = phoneFeature.toWebsitePermission(context, permissions, appSettings)
val permissionsStatus = phoneFeature.toWebsitePermission(
context,
permissions,
permissionHighlights,
appSettings
)
verify {
// Verifying phoneFeature.getActionLabel gets "Status(child of #2#4).ordinal()) was not called"
@ -177,7 +230,7 @@ class QuickSettingsFragmentStoreTest {
val defaultEnabledStatus = true
val defaultBlockedByAndroidStatus = true
val websiteInfoState = mockk<WebsiteInfoState>()
val baseWebsitePermission = WebsitePermission(
val baseWebsitePermission = WebsitePermission.Toggleable(
phoneFeature = PhoneFeature.CAMERA,
status = "",
isVisible = true,
@ -259,4 +312,15 @@ class QuickSettingsFragmentStoreTest {
assertEquals(defaultEnabledStatus, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).isEnabled)
assertEquals(defaultBlockedByAndroidStatus, store.state.websitePermissionsState.getValue(PhoneFeature.LOCATION).isBlockedByAndroid)
}
private fun getRules() = SitePermissionsRules(
camera = Action.ASK_TO_ALLOW,
location = Action.ASK_TO_ALLOW,
microphone = Action.ASK_TO_ALLOW,
notification = Action.ASK_TO_ALLOW,
autoplayAudible = AutoplayAction.BLOCKED,
autoplayInaudible = AutoplayAction.BLOCKED,
persistentStorage = Action.ASK_TO_ALLOW,
mediaKeySystemAccess = Action.ASK_TO_ALLOW
)
}

@ -38,4 +38,19 @@ class QuickSettingsInteractorTest {
assertTrue(permission.isCaptured)
assertEquals(websitePermission, permission.captured)
}
@Test
fun `onAutoplayChanged should delegate the controller`() {
val websitePermission = mockk<AutoplayValue>()
val permission = slot<AutoplayValue>()
interactor.onAutoplayChanged(websitePermission)
verify {
controller.handleAutoplayChanged(capture(permission))
}
assertTrue(permission.isCaptured)
assertEquals(websitePermission, permission.captured)
}
}

@ -0,0 +1,200 @@
/* 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 android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.widget.AppCompatSpinner
import androidx.core.view.isVisible
import io.mockk.every
import io.mockk.spyk
import io.mockk.MockKAnnotations
import io.mockk.verify
import io.mockk.impl.annotations.MockK
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertTrue
import junit.framework.TestCase.assertEquals
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.PhoneFeature
import org.mozilla.fenix.settings.quicksettings.WebsitePermissionsView.PermissionViewHolder.SpinnerPermission
import org.mozilla.fenix.settings.quicksettings.WebsitePermissionsView.PermissionViewHolder.ToggleablePermission
import java.util.EnumMap
@RunWith(FenixRobolectricTestRunner::class)
class WebsitePermissionViewTest {
@MockK(relaxed = true)
private lateinit var interactor: WebsitePermissionInteractor
private lateinit var view: WebsitePermissionsView
@Before
fun setup() {
MockKAnnotations.init(this)
view = spyk(WebsitePermissionsView(FrameLayout(testContext), interactor))
}
@Test
fun `update - with visible permissions`() {
val label = TextView(testContext)
val status = TextView(testContext)
val permission = WebsitePermission.Toggleable(
phoneFeature = PhoneFeature.CAMERA,
status = "status",
isVisible = true,
isEnabled = true,
isBlockedByAndroid = false
)
val map = mapOf<PhoneFeature, WebsitePermission>(PhoneFeature.CAMERA to permission)
view.permissionViews = EnumMap(
mapOf(PhoneFeature.CAMERA to ToggleablePermission(label, status))
)
every { view.bindPermission(any(), any()) } returns Unit
view.update(map)
verify { interactor.onPermissionsShown() }
verify { view.bindPermission(any(), any()) }
}
@Test
fun `update - with none visible permissions`() {
val label = TextView(testContext)
val status = TextView(testContext)
val permission = WebsitePermission.Toggleable(
phoneFeature = PhoneFeature.CAMERA,
status = "status",
isVisible = false,
isEnabled = true,
isBlockedByAndroid = false
)
val map = mapOf<PhoneFeature, WebsitePermission>(PhoneFeature.CAMERA to permission)
view.permissionViews =
EnumMap(mapOf(PhoneFeature.CAMERA to ToggleablePermission(label, status)))
every { view.bindPermission(any(), any()) } returns Unit
view.update(map)
verify(exactly = 0) { interactor.onPermissionsShown() }
verify { view.bindPermission(any(), any()) }
}
@Test
fun `bindPermission - a visible ToggleablePermission`() {
val label = TextView(testContext)
val status = TextView(testContext)
val permissionView = ToggleablePermission(label, status)
val permission = WebsitePermission.Toggleable(
phoneFeature = PhoneFeature.CAMERA,
status = "status",
isVisible = true,
isEnabled = true,
isBlockedByAndroid = false
)
view.permissionViews = EnumMap(mapOf(PhoneFeature.CAMERA to permissionView))
every { interactor.onPermissionToggled(any()) } returns Unit
view.bindPermission(permission, permissionView)
assertTrue(permissionView.label.isVisible)
assertTrue(permissionView.label.isEnabled)
assertTrue(permissionView.status.isVisible)
assertEquals(permission.status, permissionView.status.text)
permissionView.status.performClick()
verify { interactor.onPermissionToggled(any()) }
}
@Test
fun `bindPermission - a not visible ToggleablePermission`() {
val label = TextView(testContext)
val status = TextView(testContext)
val permissionView = ToggleablePermission(label, status)
val permission = WebsitePermission.Toggleable(
phoneFeature = PhoneFeature.CAMERA,
status = "status",
isVisible = false,
isEnabled = false,
isBlockedByAndroid = false
)
view.permissionViews = EnumMap(mapOf(PhoneFeature.CAMERA to permissionView))
every { interactor.onPermissionToggled(any()) } returns Unit
view.bindPermission(permission, permissionView)
assertFalse(permissionView.label.isVisible)
assertFalse(permissionView.label.isEnabled)
assertFalse(permissionView.status.isVisible)
assertEquals(permission.status, permissionView.status.text)
permissionView.status.performClick()
verify { interactor.onPermissionToggled(any()) }
}
@Test
fun `bindPermission - a visible SpinnerPermission`() {
val label = TextView(testContext)
val status = AppCompatSpinner(testContext)
val permissionView = SpinnerPermission(label, status)
val options = listOf(
AutoplayValue.BlockAll(
label = "BlockAll",
rules = mock(),
sitePermission = null
),
AutoplayValue.AllowAll(
label = "AllowAll",
rules = mock(),
sitePermission = null
),
AutoplayValue.BlockAudible(
label = "BlockAudible",
rules = mock(),
sitePermission = null
)
)
val permission = WebsitePermission.Autoplay(
autoplayValue = options[0],
options = options,
isVisible = true
)
view.permissionViews = EnumMap(mapOf(PhoneFeature.AUTOPLAY to permissionView))
every { interactor.onAutoplayChanged(any()) } returns Unit
view.bindPermission(permission, permissionView)
assertTrue(permissionView.label.isVisible)
assertFalse(permissionView.label.isEnabled)
assertTrue(permissionView.status.isVisible)
assertEquals(permission.autoplayValue, permissionView.status.selectedItem)
permissionView.status.onItemSelectedListener!!.onItemSelected(
mock(),
permissionView.status,
1,
0L
)
verify { interactor.onAutoplayChanged(permissionView.status.selectedItem as AutoplayValue) }
}
}

@ -578,7 +578,7 @@ class SettingsTest {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_AUDIBLE, ALLOWED)
assertEquals(
defaultPermissions.copy(autoplayAudible = ALLOWED),
defaultPermissions.copy(autoplayAudible = AutoplayAction.ALLOWED),
settings.getSitePermissionsCustomSettingsRules()
)
}
@ -588,7 +588,7 @@ class SettingsTest {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY_INAUDIBLE, ALLOWED)
assertEquals(
defaultPermissions.copy(autoplayInaudible = ALLOWED),
defaultPermissions.copy(autoplayInaudible = AutoplayAction.ALLOWED),
settings.getSitePermissionsCustomSettingsRules()
)
}

Loading…
Cancel
Save