1
0
Fork 0

For #255: Toggle Media Autoplay

master
Colin Lee 2019-09-12 14:58:25 -05:00 committed by Emily Kager
parent a470b1c74c
commit 1aa4f5a519
11 changed files with 184 additions and 71 deletions

View File

@ -55,7 +55,8 @@ class Core(private val context: Context) {
preferredColorScheme = getPreferredColorScheme(),
automaticFontSizeAdjustment = context.settings.shouldUseAutoSize,
fontInflationEnabled = context.settings.shouldUseAutoSize,
suspendMediaWhenInactive = !FeatureFlags.mediaIntegration
suspendMediaWhenInactive = !FeatureFlags.mediaIntegration,
allowAutoplayMedia = context.settings.isAutoPlayEnabled
)
GeckoEngine(context, defaultSettings, GeckoProvider.getOrCreateRuntime(context))

View File

@ -20,6 +20,7 @@ fun SitePermissions.toggle(featurePhone: PhoneFeature): SitePermissions {
PhoneFeature.LOCATION -> copy(location = location.toggle())
PhoneFeature.MICROPHONE -> copy(microphone = microphone.toggle())
PhoneFeature.NOTIFICATION -> copy(notification = notification.toggle())
PhoneFeature.AUTOPLAY -> copy() // not supported by GV or A-C yet
}
}

View File

@ -21,30 +21,51 @@ 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_PERMISSION = 4
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());
NOTIFICATION(ID_NOTIFICATION_PERMISSION, emptyArray()),
AUTOPLAY(ID_AUTOPLAY_PERMISSION, emptyArray());
fun isAndroidPermissionGranted(context: Context): Boolean {
return when (this) {
CAMERA, LOCATION, MICROPHONE -> context.isPermissionGranted(androidPermissionsList.asIterable())
NOTIFICATION -> true
NOTIFICATION, AUTOPLAY -> true
}
}
fun getActionLabel(context: Context, sitePermissions: SitePermissions? = null, settings: Settings? = null): String {
@StringRes val stringRes = when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
}
fun getActionLabel(
context: Context,
sitePermissions: SitePermissions? = null,
settings: Settings? = null
): String {
@StringRes val stringRes =
when (this) {
AUTOPLAY -> {
when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_autoplay_blocked
SitePermissions.Status.ALLOWED -> R.string.preference_option_autoplay_allowed
else -> R.string.preference_option_autoplay_allowed
}
}
else -> {
when (getStatus(sitePermissions, settings)) {
SitePermissions.Status.BLOCKED -> R.string.preference_option_phone_feature_blocked
SitePermissions.Status.NO_DECISION -> R.string.preference_option_phone_feature_ask_to_allow
SitePermissions.Status.ALLOWED -> R.string.preference_option_phone_feature_allowed
}
}
}
return context.getString(stringRes)
}
fun getStatus(sitePermissions: SitePermissions? = null, settings: Settings? = null): SitePermissions.Status {
fun getStatus(
sitePermissions: SitePermissions? = null,
settings: Settings? = null
): SitePermissions.Status {
val status = getStatus(sitePermissions) ?: settings?.let(::getAction)?.toStatus()
return requireNotNull(status)
}
@ -55,6 +76,7 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
LOCATION -> context.getString(R.string.preference_phone_feature_location)
MICROPHONE -> context.getString(R.string.preference_phone_feature_microphone)
NOTIFICATION -> context.getString(R.string.preference_phone_feature_notification)
AUTOPLAY -> context.getString(R.string.preference_browser_feature_autoplay)
}
}
@ -64,11 +86,19 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
LOCATION -> context.getPreferenceKey(R.string.pref_key_phone_feature_location)
MICROPHONE -> context.getPreferenceKey(R.string.pref_key_phone_feature_microphone)
NOTIFICATION -> context.getPreferenceKey(R.string.pref_key_phone_feature_notification)
AUTOPLAY -> context.getPreferenceKey(R.string.pref_key_browser_feature_autoplay)
}
}
fun getAction(settings: Settings): SitePermissionsRules.Action =
settings.getSitePermissionsPhoneFeatureAction(this)
settings.getSitePermissionsPhoneFeatureAction(this, getDefault())
fun getDefault(): SitePermissionsRules.Action {
return when (this) {
AUTOPLAY -> SitePermissionsRules.Action.BLOCKED
else -> SitePermissionsRules.Action.ASK_TO_ALLOW
}
}
private fun getStatus(sitePermissions: SitePermissions?): SitePermissions.Status? {
sitePermissions ?: return null
@ -77,12 +107,13 @@ enum class PhoneFeature(val id: Int, val androidPermissionsList: Array<String>)
LOCATION -> sitePermissions.location
MICROPHONE -> sitePermissions.microphone
NOTIFICATION -> sitePermissions.notification
AUTOPLAY -> SitePermissions.Status.NO_DECISION // No support from GV or A-C yet
}
}
companion object {
fun findFeatureBy(permissions: Array<out String>): PhoneFeature? {
return PhoneFeature.values().find { feature ->
return values().find { feature ->
feature.androidPermissionsList.any { permission ->
permission == permissions.first()
}

View File

@ -159,6 +159,7 @@ class SitePermissionsManageExceptionsPhoneFeatureFragment : Fragment() {
PhoneFeature.LOCATION -> sitePermissions.copy(location = status)
PhoneFeature.MICROPHONE -> sitePermissions.copy(microphone = status)
PhoneFeature.NOTIFICATION -> sitePermissions.copy(notification = status)
PhoneFeature.AUTOPLAY -> sitePermissions.copy() // not supported by GV or A-C yet
}
lifecycleScope.launch(IO) {
requireComponents.core.permissionStorage.updateSitePermissions(updatedSitePermissions)

View File

@ -25,6 +25,7 @@ import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.ASK_TO_ALLOW
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action.BLOCKED
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.utils.Settings
@ -46,25 +47,38 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
settings = requireContext().settings
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val rootView = inflater.inflate(R.layout.fragment_manage_site_permissions_feature_phone, container, false)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(
R.layout.fragment_manage_site_permissions_feature_phone,
container,
false
)
initAskToAllowRadio(rootView)
initBlockRadio(rootView)
initFirstRadio(rootView)
initSecondRadio(rootView)
bindBlockedByAndroidContainer(rootView)
return rootView
}
override fun onResume() {
super.onResume()
initBlockedByAndroidView(phoneFeature, blockedByAndroidView)
}
private fun initAskToAllowRadio(rootView: View) {
private fun initFirstRadio(rootView: View) {
val radio = rootView.findViewById<RadioButton>(R.id.ask_to_allow_radio)
val askToAllowText = getString(R.string.preference_option_phone_feature_ask_to_allow)
val askToAllowText = when (phoneFeature) {
PhoneFeature.AUTOPLAY -> getString(R.string.preference_option_autoplay_blocked)
else -> getString(R.string.preference_option_phone_feature_ask_to_allow)
}
val recommendedText = getString(R.string.phone_feature_recommended)
val recommendedTextSize = resources.getDimensionPixelSize(R.dimen.phone_feature_label_recommended_text_size)
val recommendedTextSize =
resources.getDimensionPixelSize(R.dimen.phone_feature_label_recommended_text_size)
val recommendedSpannable = SpannableString(recommendedText)
recommendedSpannable.setSpan(
@ -86,10 +100,16 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
append(recommendedSpannable)
this
}
val expectedAction = if (phoneFeature == PhoneFeature.AUTOPLAY) BLOCKED else ASK_TO_ALLOW
radio.setOnClickListener {
saveActionInSettings(ASK_TO_ALLOW)
if (phoneFeature == PhoneFeature.AUTOPLAY) {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY, expectedAction)
requireComponents.core.engine.settings.allowAutoplayMedia = false
} else {
saveActionInSettings(expectedAction)
}
}
radio.restoreState(ASK_TO_ALLOW)
radio.restoreState(expectedAction)
}
private fun RadioButton.restoreState(action: SitePermissionsRules.Action) {
@ -99,12 +119,22 @@ class SitePermissionsManagePhoneFeatureFragment : Fragment() {
}
}
private fun initBlockRadio(rootView: View) {
private fun initSecondRadio(rootView: View) {
val radio = rootView.findViewById<RadioButton>(R.id.block_radio)
radio.setOnClickListener {
saveActionInSettings(BLOCKED)
radio.text = when (phoneFeature) {
PhoneFeature.AUTOPLAY -> getString(R.string.preference_option_autoplay_allowed)
else -> getString(R.string.preference_option_phone_feature_blocked)
}
radio.restoreState(BLOCKED)
val expectedAction = if (phoneFeature == PhoneFeature.AUTOPLAY) ASK_TO_ALLOW else BLOCKED
radio.setOnClickListener {
if (phoneFeature == PhoneFeature.AUTOPLAY) {
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.AUTOPLAY, expectedAction)
requireComponents.core.engine.settings.allowAutoplayMedia = true
} else {
saveActionInSettings(expectedAction)
}
}
radio.restoreState(expectedAction)
}
private fun bindBlockedByAndroidContainer(rootView: View) {

View File

@ -48,16 +48,22 @@ class QuickSettingsComponent(
return if (sitePermissions == null) {
val settings = context.settings
val origin = requireNotNull(url.toUri().host)
var location = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION).toStatus()
var camera = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA).toStatus()
var microphone = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE).toStatus()
var notification = settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION).toStatus()
var location =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION).toStatus()
var camera =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA).toStatus()
var microphone =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE).toStatus()
var notification =
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION).toStatus()
when (featurePhone) {
PhoneFeature.CAMERA -> camera = camera.toggle()
PhoneFeature.LOCATION -> location = location.toggle()
PhoneFeature.MICROPHONE -> microphone = microphone.toggle()
PhoneFeature.NOTIFICATION -> notification = notification.toggle()
PhoneFeature.AUTOPLAY -> { // not supported by GV or A-C yet
}
}
context.components.core.permissionStorage
.addSitePermissionException(origin, location, notification, microphone, camera)
@ -105,45 +111,58 @@ sealed class QuickSettingsChange : Change {
val sitePermissions: SitePermissions?
) : QuickSettingsChange()
data class PermissionGranted(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) :
data class PermissionGranted(
val phoneFeature: PhoneFeature,
val sitePermissions: SitePermissions?
) :
QuickSettingsChange()
data class PromptRestarted(val sitePermissions: SitePermissions?) : QuickSettingsChange()
data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) : QuickSettingsChange()
data class Stored(val phoneFeature: PhoneFeature, val sitePermissions: SitePermissions?) :
QuickSettingsChange()
}
class QuickSettingsViewModel(
initialState: QuickSettingsState
) : UIComponentViewModelBase<QuickSettingsState, QuickSettingsChange>(initialState, reducer) {
companion object {
val reducer: (QuickSettingsState, QuickSettingsChange) -> QuickSettingsState = { state, change ->
when (change) {
is QuickSettingsChange.Change -> {
state.copy(
mode = QuickSettingsState.Mode.Normal(
change.url,
change.isSecured,
change.isTrackingProtectionOn,
change.sitePermissions
val reducer: (QuickSettingsState, QuickSettingsChange) -> QuickSettingsState =
{ state, change ->
when (change) {
is QuickSettingsChange.Change -> {
state.copy(
mode = QuickSettingsState.Mode.Normal(
change.url,
change.isSecured,
change.isTrackingProtectionOn,
change.sitePermissions
)
)
)
}
is QuickSettingsChange.PermissionGranted -> {
state.copy(
mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions)
)
}
is QuickSettingsChange.PromptRestarted -> {
state.copy(
mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid(change.sitePermissions)
)
}
is QuickSettingsChange.Stored -> {
state.copy(
mode = QuickSettingsState.Mode.ActionLabelUpdated(change.phoneFeature, change.sitePermissions)
)
}
is QuickSettingsChange.PermissionGranted -> {
state.copy(
mode = QuickSettingsState.Mode.ActionLabelUpdated(
change.phoneFeature,
change.sitePermissions
)
)
}
is QuickSettingsChange.PromptRestarted -> {
state.copy(
mode = QuickSettingsState.Mode.CheckPendingFeatureBlockedByAndroid(
change.sitePermissions
)
)
}
is QuickSettingsChange.Stored -> {
state.copy(
mode = QuickSettingsState.Mode.ActionLabelUpdated(
change.phoneFeature,
change.sitePermissions
)
)
}
}
}
}
}
}

View File

@ -11,6 +11,7 @@ import android.content.SharedPreferences
import androidx.annotation.VisibleForTesting
import androidx.annotation.VisibleForTesting.PRIVATE
import mozilla.components.feature.sitepermissions.SitePermissionsRules
import mozilla.components.feature.sitepermissions.SitePermissionsRules.Action
import mozilla.components.support.ktx.android.content.PreferencesHolder
import mozilla.components.support.ktx.android.content.booleanPreference
import mozilla.components.support.ktx.android.content.floatPreference
@ -42,14 +43,14 @@ class Settings private constructor(
private const val CFR_COUNT_CONDITION_FOCUS_INSTALLED = 1
private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3
private fun actionToInt(action: SitePermissionsRules.Action) = when (action) {
SitePermissionsRules.Action.BLOCKED -> BLOCKED_INT
SitePermissionsRules.Action.ASK_TO_ALLOW -> ASK_TO_ALLOW_INT
private fun actionToInt(action: Action) = when (action) {
Action.BLOCKED -> BLOCKED_INT
Action.ASK_TO_ALLOW -> ASK_TO_ALLOW_INT
}
private fun intToAction(action: Int) = when (action) {
BLOCKED_INT -> SitePermissionsRules.Action.BLOCKED
ASK_TO_ALLOW_INT -> SitePermissionsRules.Action.ASK_TO_ALLOW
BLOCKED_INT -> Action.BLOCKED
ASK_TO_ALLOW_INT -> Action.ASK_TO_ALLOW
else -> throw InvalidParameterException("$action is not a valid SitePermissionsRules.Action")
}
@ -86,10 +87,10 @@ class Settings private constructor(
val isCrashReportingEnabled: Boolean
get() = isCrashReportEnabledInBuild &&
preferences.getBoolean(
appContext.getPreferenceKey(R.string.pref_key_crash_reporter),
true
)
preferences.getBoolean(
appContext.getPreferenceKey(R.string.pref_key_crash_reporter),
true
)
val isRemoteDebuggingEnabled by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_remote_debugging),
@ -106,11 +107,15 @@ class Settings private constructor(
default = true
)
val isAutoPlayEnabled = getSitePermissionsPhoneFeatureAction(
PhoneFeature.AUTOPLAY, Action.BLOCKED
) != Action.BLOCKED
private var trackingProtectionOnboardingShownThisSession = false
val shouldShowTrackingProtectionOnboarding: Boolean
get() = trackingProtectionOnboardingCount < trackingProtectionOnboardingMaximumCount &&
!trackingProtectionOnboardingShownThisSession
!trackingProtectionOnboardingShownThisSession
val shouldAutoBounceQuickActionSheet: Boolean
get() = autoBounceQuickActionSheetCount < autoBounceMaximumCount
@ -226,12 +231,15 @@ class Settings private constructor(
).apply()
}
fun getSitePermissionsPhoneFeatureAction(feature: PhoneFeature) =
intToAction(preferences.getInt(feature.getPreferenceKey(appContext), ASK_TO_ALLOW_INT))
fun getSitePermissionsPhoneFeatureAction(
feature: PhoneFeature,
default: Action = Action.ASK_TO_ALLOW
) =
intToAction(preferences.getInt(feature.getPreferenceKey(appContext), actionToInt(default)))
fun setSitePermissionsPhoneFeatureAction(
feature: PhoneFeature,
value: SitePermissionsRules.Action
value: Action
) {
preferences.edit().putInt(feature.getPreferenceKey(appContext), actionToInt(value)).apply()
}
@ -295,7 +303,7 @@ class Settings private constructor(
val showCondition =
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_INSTALLED && focusInstalled) ||
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled)
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled)
if (showCondition && !showedPrivateModeContextualFeatureRecommender) {
showedPrivateModeContextualFeatureRecommender = true

View File

@ -0,0 +1,9 @@
<?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 android:autoMirrored="true" android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M10.5,16.357l6.01,-3.492a1,1 0,0 0,0 -1.73L10.5,7.643A1,1 0,0 0,9 8.508v6.984a1,1 0,0 0,1.5 0.865zM15.33,2.569a1,1 0,1 0,-0.666 1.885A8.023,8.023 0,1 1,7 5.773L7,9.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 1h3.524a9.984,9.984 0,1 0,9.308 -1.431z"/>
</vector>

View File

@ -74,6 +74,7 @@
<string name="pref_key_show_site_exceptions" translatable="false">pref_key_show_site_exceptions</string>
<string name="pref_key_recommended_settings" translatable="false">pref_key_recommended_settings</string>
<string name="pref_key_custom_settings" translatable="false">pref_key_custom_settings</string>
<string name="pref_key_browser_feature_autoplay" translatable="false">pref_key_browser_feature_autoplay</string>
<string name="pref_key_phone_feature_camera" translatable="false">pref_key_phone_feature_camera</string>
<string name="pref_key_phone_feature_location" translatable="false">pref_key_phone_feature_location</string>
<string name="pref_key_phone_feature_microphone" translatable="false">pref_key_phone_feature_microphone</string>

View File

@ -504,6 +504,8 @@
<string name="clear_permission">Clear permission</string>
<!-- Button label for clearing all the information on all sites-->
<string name="clear_permissions_on_all_sites">Clear permissions on all sites</string>
<!-- Preference for altering video and audio autoplay for all websites -->
<string name="preference_browser_feature_autoplay">Autoplay</string>
<!-- Preference for altering the camera access for all websites -->
<string name="preference_phone_feature_camera">Camera</string>
<!-- Preference for altering the microphone access for all websites -->
@ -526,6 +528,10 @@
<string name="tracking_protection_on">On</string>
<!-- Summary of tracking protection preference if tracking protection is set to off -->
<string name="tracking_protection_off">Off</string>
<!-- Label that indicates video and audio autoplay is blocked -->
<string name="preference_option_autoplay_blocked">Video and audio blocked</string>
<!-- Label that indicates video and audio autoplay is allowed -->
<string name="preference_option_autoplay_allowed">Video and audio allowed</string>
<!-- Collections -->
<!-- Label to describe what collections are to a new user without any collections -->

View File

@ -5,6 +5,12 @@
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.preference.Preference
android:icon="@drawable/ic_autoplay_enabled"
android:key="@string/pref_key_browser_feature_autoplay"
android:title="@string/preference_browser_feature_autoplay"
android:summary="@string/preference_option_autoplay_blocked"/>
<androidx.preference.Preference
android:icon="@drawable/ic_camera_enabled"
android:key="@string/pref_key_phone_feature_camera"