diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index 2e605583e..02a2db9d0 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -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)) diff --git a/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt b/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt index e46ba48c4..59312eac4 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/Extensions.kt @@ -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 } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt index 2f233f28e..54383b4dc 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PhoneFeature.kt @@ -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) { 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) 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) 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) 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): PhoneFeature? { - return PhoneFeature.values().find { feature -> + return values().find { feature -> feature.androidPermissionsList.any { permission -> permission == permissions.first() } diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt index 6e0210694..8a56ab1b3 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManageExceptionsPhoneFeatureFragment.kt @@ -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) diff --git a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManagePhoneFeatureFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManagePhoneFeatureFragment.kt index 9a7eb0815..c25c250d8 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManagePhoneFeatureFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SitePermissionsManagePhoneFeatureFragment.kt @@ -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(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(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) { diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt index 542e12f67..0f9023cd5 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsComponent.kt @@ -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(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 + ) + ) + } } } - } } } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 8f3590f53..3df86a355 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -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 diff --git a/app/src/main/res/drawable/ic_autoplay_enabled.xml b/app/src/main/res/drawable/ic_autoplay_enabled.xml new file mode 100644 index 000000000..60154b0ec --- /dev/null +++ b/app/src/main/res/drawable/ic_autoplay_enabled.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 6dc66ff08..4e1df3f66 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -74,6 +74,7 @@ pref_key_show_site_exceptions pref_key_recommended_settings pref_key_custom_settings + pref_key_browser_feature_autoplay pref_key_phone_feature_camera pref_key_phone_feature_location pref_key_phone_feature_microphone diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 05929f178..0796dbddc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -504,6 +504,8 @@ Clear permission Clear permissions on all sites + + Autoplay Camera @@ -526,6 +528,10 @@ On Off + + Video and audio blocked + + Video and audio allowed diff --git a/app/src/main/res/xml/site_permissions_preferences.xml b/app/src/main/res/xml/site_permissions_preferences.xml index f3d9cc515..955288e13 100644 --- a/app/src/main/res/xml/site_permissions_preferences.xml +++ b/app/src/main/res/xml/site_permissions_preferences.xml @@ -5,6 +5,12 @@ + +