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 da3e5eb1c..ab9367420 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -25,6 +25,8 @@ import mozilla.components.browser.storage.sync.PlacesHistoryStorage import mozilla.components.concept.engine.DefaultSettings import mozilla.components.concept.engine.Engine import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.CookiePolicy +import mozilla.components.concept.engine.EngineSession.TrackingProtectionPolicy.TrackingCategory import mozilla.components.concept.engine.mediaquery.PreferredColorScheme import mozilla.components.concept.fetch.Client import mozilla.components.feature.customtabs.store.CustomTabsServiceStore @@ -241,13 +243,24 @@ class Core(private val context: Context) { * in private browsing mode, default to the current preference value. * @return the constructed tracking protection policy based on preferences. */ + @Suppress("ComplexMethod") fun createTrackingProtectionPolicy( normalMode: Boolean = context.settings().shouldUseTrackingProtection, privateMode: Boolean = true ): TrackingProtectionPolicy { val trackingProtectionPolicy = - if (context.settings().useStrictTrackingProtection) TrackingProtectionPolicy.strict() else - TrackingProtectionPolicy.recommended() + when { + context.settings().useStrictTrackingProtection -> TrackingProtectionPolicy.strict() + context.settings().useCustomTrackingProtection -> return TrackingProtectionPolicy.select( + cookiePolicy = geCustomCookiePolicy(), + trackingCategories = getCustomTrackingCategories() + ).apply { + if (context.settings().blockTrackingContentSelectionInCustomTrackingProtection == "private") { + forPrivateSessionsOnly() + } + } + else -> TrackingProtectionPolicy.recommended() + } return when { normalMode && privateMode -> trackingProtectionPolicy @@ -257,6 +270,39 @@ class Core(private val context: Context) { } } + private fun geCustomCookiePolicy(): CookiePolicy { + return when (context.settings().blockCookiesSelectionInCustomTrackingProtection) { + "all" -> CookiePolicy.ACCEPT_NONE + "social" -> CookiePolicy.ACCEPT_NON_TRACKERS + "unvisited" -> CookiePolicy.ACCEPT_VISITED + "third-party" -> CookiePolicy.ACCEPT_ONLY_FIRST_PARTY + else -> CookiePolicy.ACCEPT_NONE + } + } + + private fun getCustomTrackingCategories(): Array { + val categories = arrayListOf( + TrackingCategory.AD, + TrackingCategory.ANALYTICS, + TrackingCategory.SOCIAL, + TrackingCategory.MOZILLA_SOCIAL + ) + + if (context.settings().blockTrackingContentInCustomTrackingProtection) { + categories.add(TrackingCategory.SCRIPTS_AND_SUB_RESOURCES) + } + + if (context.settings().blockFingerprintersInCustomTrackingProtection) { + categories.add(TrackingCategory.FINGERPRINTING) + } + + if (context.settings().blockCryptominersInCustomTrackingProtection) { + categories.add(TrackingCategory.CRYPTOMINING) + } + + return categories.toTypedArray() + } + /** * Sets Preferred Color scheme based on Dark/Light Theme Settings or Current Configuration */ diff --git a/app/src/main/java/org/mozilla/fenix/settings/DropDownListPreference.kt b/app/src/main/java/org/mozilla/fenix/settings/DropDownListPreference.kt new file mode 100644 index 000000000..9a14caa55 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/DropDownListPreference.kt @@ -0,0 +1,25 @@ +/* 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 + +import android.content.Context +import android.util.AttributeSet +import android.widget.ArrayAdapter +import androidx.preference.DropDownPreference +import org.mozilla.fenix.R + +class DropDownListPreference @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null +) : DropDownPreference(context, attrs) { + + init { + layoutResource = R.layout.dropdown_preference_etp + } + + override fun createAdapter(): ArrayAdapter { + return ArrayAdapter(context, R.layout.etp_dropdown_item) + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt index 7b81f2503..1c628e324 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/TrackingProtectionFragment.kt @@ -6,6 +6,8 @@ package org.mozilla.fenix.settings import android.os.Bundle import androidx.navigation.findNavController +import androidx.preference.CheckBoxPreference +import androidx.preference.DropDownPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.SwitchPreference @@ -34,9 +36,21 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { } private lateinit var radioStrict: RadioButtonInfoPreference private lateinit var radioStandard: RadioButtonInfoPreference + private lateinit var radioCustom: RadioButtonInfoPreference + private lateinit var customCookies: CheckBoxPreference + private lateinit var customCookiesSelect: DropDownPreference + private lateinit var customTracking: CheckBoxPreference + private lateinit var customTrackingSelect: DropDownPreference + private lateinit var customCryptominers: CheckBoxPreference + private lateinit var customFingerprinters: CheckBoxPreference override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.tracking_protection_preferences, rootKey) + bindStrict() + bindStandard() + bindCustom() + setupRadioGroups() + updateCustomOptionsVisibility() } override fun onResume() { @@ -59,10 +73,6 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { true } - bindStrict() - bindStandard() - setupRadioGroups() - val trackingProtectionLearnMore = context!!.getPreferenceKey(R.string.pref_key_etp_learn_more) val learnMorePreference = findPreference(trackingProtectionLearnMore) @@ -100,6 +110,7 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { ) ) } + updateCustomOptionsVisibility() return super.onPreferenceChange(preference, newValue) } } @@ -107,7 +118,9 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { nav( R.id.trackingProtectionFragment, TrackingProtectionFragmentDirections - .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(true) + .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment( + getString(R.string.preference_enhanced_tracking_protection_strict_default) + ) ) } } @@ -127,6 +140,7 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { ) ) } + updateCustomOptionsVisibility() return super.onPreferenceChange(preference, newValue) } } @@ -134,11 +148,104 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { nav( R.id.trackingProtectionFragment, TrackingProtectionFragmentDirections - .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(false) + .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment( + getString(R.string.preference_enhanced_tracking_protection_standard) + ) ) } } + private fun bindCustom() { + val keyCustom = getString(R.string.pref_key_tracking_protection_custom_option) + radioCustom = requireNotNull(findPreference(keyCustom)) + radioCustom.contentDescription = + getString(R.string.preference_enhanced_tracking_protection_custom_info_button) + radioCustom.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + if (newValue == true) { + updateTrackingProtectionPolicy() + } + updateCustomOptionsVisibility() + return super.onPreferenceChange(preference, newValue) + } + } + radioCustom.onInfoClickListener { + nav( + R.id.trackingProtectionFragment, + TrackingProtectionFragmentDirections + .actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment( + getString(R.string.preference_enhanced_tracking_protection_custom) + ) + ) + } + + customCookies = requireNotNull( + findPreference( + getString(R.string.pref_key_tracking_protection_custom_cookies) + ) + ) + + customCookiesSelect = requireNotNull( + findPreference( + getString(R.string.pref_key_tracking_protection_custom_cookies_select) + ) + ) + customTracking = requireNotNull( + findPreference( + getString(R.string.pref_key_tracking_protection_custom_tracking_content) + ) + ) + customTrackingSelect = requireNotNull( + findPreference( + getString(R.string.pref_key_tracking_protection_custom_tracking_content_select) + ) + ) + customCryptominers = requireNotNull( + findPreference( + getString(R.string.pref_key_tracking_protection_custom_cryptominers) + ) + ) + customFingerprinters = requireNotNull( + findPreference( + getString(R.string.pref_key_tracking_protection_custom_fingerprinters) + ) + ) + + customCookies.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + updateTrackingProtectionPolicy() + customCookiesSelect.isVisible = !customCookies.isChecked + return super.onPreferenceChange(preference, newValue) + } + } + + customTracking.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + updateTrackingProtectionPolicy() + customTrackingSelect.isVisible = !customTracking.isChecked + return super.onPreferenceChange(preference, newValue) + } + } + + customCookiesSelect.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + updateTrackingProtectionPolicy() + customTrackingSelect.isVisible = !customTracking.isChecked + return super.onPreferenceChange(preference, newValue) + } + } + + customTrackingSelect.onPreferenceChangeListener = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + updateTrackingProtectionPolicy() + customTrackingSelect.isVisible = !customTracking.isChecked + return super.onPreferenceChange(preference, newValue) + } + } + + updateCustomOptionsVisibility() + } + private fun updateTrackingProtectionPolicy() { context?.components?.let { val policy = it.core.createTrackingProtectionPolicy() @@ -150,5 +257,21 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() { private fun setupRadioGroups() { radioStandard.addToRadioGroup(radioStrict) radioStrict.addToRadioGroup(radioStandard) + + radioStandard.addToRadioGroup(radioCustom) + radioCustom.addToRadioGroup(radioStandard) + + radioStrict.addToRadioGroup(radioCustom) + radioCustom.addToRadioGroup(radioStrict) + } + + private fun updateCustomOptionsVisibility() { + val isCustomSelected = requireContext().settings().useCustomTrackingProtection + customCookies.isVisible = isCustomSelected + customCookiesSelect.isVisible = isCustomSelected && customCookies.isChecked + customTracking.isVisible = isCustomSelected + customTrackingSelect.isVisible = isCustomSelected && customTracking.isChecked + customCryptominers.isVisible = isCustomSelected + customFingerprinters.isVisible = isCustomSelected } } diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt index aa7ea7609..10f512345 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionBlockingFragment.kt @@ -11,29 +11,47 @@ import androidx.fragment.app.Fragment import androidx.navigation.fragment.navArgs import kotlinx.android.synthetic.main.fragment_tracking_protection_blocking.* import org.mozilla.fenix.R +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar class TrackingProtectionBlockingFragment : Fragment(R.layout.fragment_tracking_protection_blocking) { private val args: TrackingProtectionBlockingFragmentArgs by navArgs() + private var isCustomProtection: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + isCustomProtection = requireContext().settings().useCustomTrackingProtection + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - category_fingerprinters.isVisible = args.strictMode - category_tracking_content.isVisible = args.strictMode + + when (args.protectionMode) { + getString(R.string.preference_enhanced_tracking_protection_strict) -> { + category_fingerprinters.isVisible = true + category_tracking_content.isVisible = true + } + + getString(R.string.preference_enhanced_tracking_protection_custom) -> { + category_fingerprinters.isVisible = + requireContext().settings().blockFingerprintersInCustomTrackingProtection + category_cryptominers.isVisible = + requireContext().settings().blockCryptominersInCustomTrackingProtection + category_cookies.isVisible = + requireContext().settings().blockCookiesInCustomTrackingProtection + } + + getString(R.string.preference_enhanced_tracking_protection_standard) -> return + + else -> return + } } override fun onResume() { super.onResume() - showToolbar(getTitle()) - } - - private fun getTitle(): String { - return if (args.strictMode) { - getString(R.string.preference_enhanced_tracking_protection_strict_default) - } else { - getString(R.string.preference_enhanced_tracking_protection_standard_option) - } + showToolbar(args.protectionMode) } } 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 37c9e7e71..f62d15484 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -225,6 +225,40 @@ class Settings private constructor( true ) + val useCustomTrackingProtection by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_option), + false + ) + + val blockCookiesInCustomTrackingProtection by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies), + true + ) + val blockCookiesSelectionInCustomTrackingProtection by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cookies_select), + "" + ) + + val blockTrackingContentInCustomTrackingProtection by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_tracking_content), + true + ) + + val blockTrackingContentSelectionInCustomTrackingProtection by stringPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_tracking_content_select), + "" + ) + + val blockCryptominersInCustomTrackingProtection by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_cryptominers), + true + ) + + val blockFingerprintersInCustomTrackingProtection by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_tracking_protection_custom_fingerprinters), + true + ) + val shouldUseFixedTopToolbar: Boolean get() { return touchExplorationIsEnabled || switchServiceIsEnabled diff --git a/app/src/main/res/drawable/etp_spinner_item_background.xml b/app/src/main/res/drawable/etp_spinner_item_background.xml new file mode 100644 index 000000000..642b276e3 --- /dev/null +++ b/app/src/main/res/drawable/etp_spinner_item_background.xml @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/rounded_grey_corners_transparent_center.xml b/app/src/main/res/drawable/rounded_grey_corners_transparent_center.xml new file mode 100644 index 000000000..e933d4ef2 --- /dev/null +++ b/app/src/main/res/drawable/rounded_grey_corners_transparent_center.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/checkbox_left_preference_etp.xml b/app/src/main/res/layout/checkbox_left_preference_etp.xml new file mode 100644 index 000000000..27a254878 --- /dev/null +++ b/app/src/main/res/layout/checkbox_left_preference_etp.xml @@ -0,0 +1,54 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/dropdown_preference_etp.xml b/app/src/main/res/layout/dropdown_preference_etp.xml new file mode 100644 index 000000000..961b8d937 --- /dev/null +++ b/app/src/main/res/layout/dropdown_preference_etp.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/layout/etp_dropdown_item.xml b/app/src/main/res/layout/etp_dropdown_item.xml new file mode 100644 index 000000000..e0ce6d9f9 --- /dev/null +++ b/app/src/main/res/layout/etp_dropdown_item.xml @@ -0,0 +1,14 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 3f87d7c21..5b736e426 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -665,8 +665,8 @@ android:id="@+id/trackingProtectionBlockingFragment" android:name="org.mozilla.fenix.trackingprotection.TrackingProtectionBlockingFragment"> + android:name="protectionMode" + app:argType="string" /> + + + @string/preference_enhanced_tracking_protection_custom_cookies_1 + @string/preference_enhanced_tracking_protection_custom_cookies_2 + @string/preference_enhanced_tracking_protection_custom_cookies_3 + @string/preference_enhanced_tracking_protection_custom_cookies_4 + + + + social + unvisited + third-party + all + + + + @string/preference_enhanced_tracking_protection_custom_tracking_content_1 + @string/preference_enhanced_tracking_protection_custom_tracking_content_2 + + + + all + private + + \ No newline at end of file diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index d56ebd301..83401bb96 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -113,6 +113,14 @@ pref_key_tracking_protection_exceptions pref_key_tracking_protection_standard_option pref_key_tracking_protection_strict_default + pref_key_tracking_protection_custom_option + pref_key_tracking_protection_custom_cookies + pref_key_tracking_protection_custom_cookies_select + pref_key_tracking_protection_custom_tracking_content + pref_key_tracking_protection_custom_tracking_content_select + pref_key_tracking_protection_custom_cryptominers + pref_key_tracking_protection_custom_fingerprinters + pref_key_tracking_protection_onboarding diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2dee02041..8c00951b8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -961,7 +961,35 @@ Stronger protection, but may cause some sites or content to break. What’s blocked by strict tracking protection + + Custom + + Choose which trackers and scripts to block + + What’s blocked by custom tracking protection + + Cookies + + Cross-site and social media trackers + + Cookies from unvisited sites + + All third-party cookies (may cause websites to break) + + All cookies (will cause websites to break) + + Tracking content + + In all tabs + + Only in Private tabs + + Only in Custom tabs + + Cryptominers + + Fingerprinters Blocked Allowed diff --git a/app/src/main/res/xml/tracking_protection_preferences.xml b/app/src/main/res/xml/tracking_protection_preferences.xml index 06fec1fdc..31e4c8865 100644 --- a/app/src/main/res/xml/tracking_protection_preferences.xml +++ b/app/src/main/res/xml/tracking_protection_preferences.xml @@ -27,6 +27,50 @@ android:key="@string/pref_key_tracking_protection_strict_default" android:summary="@string/preference_enhanced_tracking_protection_strict_default_description" android:title="@string/preference_enhanced_tracking_protection_strict_default" /> + + + + + + + + +