1
0
Fork 0

Simplify `PhoneFeature` code (#10810)

master
Tiger Oakes 2020-06-08 13:02:42 -07:00 committed by GitHub
parent 74948cb3f1
commit 1a19b06227
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 399 additions and 877 deletions

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

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

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