Skip to content
This repository has been archived by the owner on Feb 20, 2023. It is now read-only.

Simplify PhoneFeature code #10810

Merged
merged 2 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 19 additions & 32 deletions app/src/main/java/org/mozilla/fenix/settings/Extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

/**
Expand All @@ -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.
Expand Down
43 changes: 12 additions & 31 deletions app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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())
NotWoods marked this conversation as resolved.
Show resolved Hide resolved
}

@Suppress("ComplexMethod")
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -90,21 +90,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)
)
Expand All @@ -119,7 +118,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)
)
Expand Down Expand Up @@ -152,64 +151,14 @@ 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]
*
* @param phoneFeature [PhoneFeature] to toggle [SitePermissions] for.
*/
private fun navigateToManagePhoneFeature(phoneFeature: PhoneFeature) {
val directions = QuickSettingsSheetDialogFragmentDirections
.actionGlobalSitePermissionsManagePhoneFeature(phoneFeature.id)
.actionGlobalSitePermissionsManagePhoneFeature(phoneFeature)
navController.navigate(directions)
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}