1
0
Fork 0

Tests and cleanup for tracking protection

master
Tiger Oakes 2020-06-01 08:55:43 -07:00 committed by Emily Kager
parent 9ef4d9bdae
commit 50441e28f1
12 changed files with 249 additions and 165 deletions

View File

@ -17,10 +17,11 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.metrics
import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.trackingprotection.TrackingProtectionMode
/**
* Displays the toggle for tracking protection, options for tracking protection policy and a button
@ -34,9 +35,6 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
requireView().findNavController().navigate(directions)
true
}
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
@ -46,10 +44,10 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.tracking_protection_preferences, rootKey)
bindStrict()
bindStandard()
bindCustom()
setupRadioGroups()
val radioStrict = bindTrackingProtectionRadio(TrackingProtectionMode.STRICT)
val radioStandard = bindTrackingProtectionRadio(TrackingProtectionMode.STANDARD)
val radioCustom = bindCustom()
setupRadioGroups(radioStrict, radioStandard, radioCustom)
updateCustomOptionsVisibility()
}
@ -96,79 +94,42 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
preferenceExceptions?.onPreferenceClickListener = exceptionsClickListener
}
private fun bindStrict() {
val keyStrict = getString(R.string.pref_key_tracking_protection_strict_default)
radioStrict = requireNotNull(findPreference(keyStrict))
radioStrict.contentDescription =
getString(R.string.preference_enhanced_tracking_protection_strict_info_button)
private fun bindTrackingProtectionRadio(
mode: TrackingProtectionMode
): RadioButtonInfoPreference {
val radio = requireNotNull(findPreference<RadioButtonInfoPreference>(
getPreferenceKey(mode.preferenceKey)
))
radio.contentDescription = getString(mode.contentDescriptionRes)
radioStrict.onClickListener {
val metrics = requireComponents.analytics.metrics
radio.onClickListener {
updateCustomOptionsVisibility()
updateTrackingProtectionPolicy()
context?.metrics?.track(
Event.TrackingProtectionSettingChanged(
Event.TrackingProtectionSettingChanged.Setting.STRICT
)
)
}
radioStrict.onInfoClickListener {
nav(
R.id.trackingProtectionFragment,
TrackingProtectionFragmentDirections
.actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(
getString(R.string.preference_enhanced_tracking_protection_strict_default)
)
)
}
}
private fun bindStandard() {
val keyStandard = getString(R.string.pref_key_tracking_protection_standard_option)
radioStandard = requireNotNull(findPreference(keyStandard))
radioStandard.contentDescription =
getString(R.string.preference_enhanced_tracking_protection_standard_info_button)
radioStandard.onClickListener {
updateCustomOptionsVisibility()
updateTrackingProtectionPolicy()
context?.metrics?.track(
Event.TrackingProtectionSettingChanged(
when (mode) {
TrackingProtectionMode.STANDARD ->
Event.TrackingProtectionSettingChanged.Setting.STANDARD
)
)
TrackingProtectionMode.STRICT ->
Event.TrackingProtectionSettingChanged.Setting.STRICT
TrackingProtectionMode.CUSTOM -> null
}?.let { setting ->
metrics.track(Event.TrackingProtectionSettingChanged(setting))
}
}
radioStandard.onInfoClickListener {
radio.onInfoClickListener {
nav(
R.id.trackingProtectionFragment,
TrackingProtectionFragmentDirections
.actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(
getString(R.string.preference_enhanced_tracking_protection_standard_option)
)
.actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(mode)
)
}
return radio
}
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.onClickListener {
updateCustomOptionsVisibility()
updateTrackingProtectionPolicy()
}
radioCustom.onInfoClickListener {
nav(
R.id.trackingProtectionFragment,
TrackingProtectionFragmentDirections
.actionTrackingProtectionFragmentToTrackingProtectionBlockingFragment(
getString(R.string.preference_enhanced_tracking_protection_custom)
)
)
}
private fun bindCustom(): RadioButtonInfoPreference {
val radio = bindTrackingProtectionRadio(TrackingProtectionMode.CUSTOM)
customCookies = requireNotNull(
findPreference(
@ -254,6 +215,8 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
}
updateCustomOptionsVisibility()
return radio
}
private fun updateTrackingProtectionPolicy() {
@ -265,7 +228,11 @@ class TrackingProtectionFragment : PreferenceFragmentCompat() {
}
}
private fun setupRadioGroups() {
private fun setupRadioGroups(
radioStrict: RadioButtonInfoPreference,
radioStandard: RadioButtonInfoPreference,
radioCustom: RadioButtonInfoPreference
) {
radioStandard.addToRadioGroup(radioStrict)
radioStrict.addToRadioGroup(radioStandard)

View File

@ -29,10 +29,7 @@ class SwitchWithDescription @JvmOverloads constructor(
R.drawable.ic_tracking_protection
)
switch_widget.putCompoundDrawablesRelativeWithIntrinsicBounds(
start = AppCompatResources.getDrawable(
context,
id
)
start = AppCompatResources.getDrawable(context, id)
)
trackingProtectionCategoryTitle.text = resources.getString(
getResourceId(

View File

@ -53,7 +53,7 @@ class TrackerBuckets {
/**
* Gets the tracker URLs for a given category.
*/
operator fun get(key: TrackingProtectionCategory, blocked: Boolean) =
fun get(key: TrackingProtectionCategory, blocked: Boolean) =
if (blocked) buckets.blockedBucketMap[key].orEmpty() else buckets.loadedBucketMap[key].orEmpty()
companion object {

View File

@ -18,42 +18,33 @@ 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
}
private val settings by lazy { requireContext().settings() }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
when (args.protectionMode) {
getString(R.string.preference_enhanced_tracking_protection_standard_option) -> {
TrackingProtectionMode.STANDARD -> {
category_tracking_content.isVisible = false
}
getString(R.string.preference_enhanced_tracking_protection_strict) -> return
TrackingProtectionMode.STRICT -> {}
getString(R.string.preference_enhanced_tracking_protection_custom) -> {
TrackingProtectionMode.CUSTOM -> {
category_fingerprinters.isVisible =
requireContext().settings().blockFingerprintersInCustomTrackingProtection
settings.blockFingerprintersInCustomTrackingProtection
category_cryptominers.isVisible =
requireContext().settings().blockCryptominersInCustomTrackingProtection
settings.blockCryptominersInCustomTrackingProtection
category_cookies.isVisible =
requireContext().settings().blockCookiesInCustomTrackingProtection
settings.blockCookiesInCustomTrackingProtection
category_tracking_content.isVisible =
requireContext().settings().blockTrackingContentInCustomTrackingProtection
settings.blockTrackingContentInCustomTrackingProtection
}
else -> return
}
}
override fun onResume() {
super.onResume()
showToolbar(args.protectionMode)
showToolbar(getString(args.protectionMode.titleRes))
}
}

View File

@ -7,6 +7,7 @@ package org.mozilla.fenix.trackingprotection
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.withStyledAttributes
import kotlinx.android.synthetic.main.tracking_protection_category.view.*
@ -30,7 +31,7 @@ class TrackingProtectionCategoryItem @JvmOverloads constructor(
R.styleable.TrackingProtectionCategory_categoryItemIcon,
R.drawable.ic_cryptominers
)
trackingProtectionCategoryIcon?.background = resources.getDrawable(id, context.theme)
trackingProtectionCategoryIcon?.background = AppCompatResources.getDrawable(context, id)
trackingProtectionCategoryTitle?.text = resources.getString(
getResourceId(
R.styleable.TrackingProtectionCategory_categoryItemTitle,

View File

@ -0,0 +1,34 @@
/* 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.trackingprotection
import android.os.Parcelable
import androidx.annotation.StringRes
import kotlinx.android.parcel.Parcelize
import org.mozilla.fenix.R
@Parcelize
enum class TrackingProtectionMode(
@StringRes val preferenceKey: Int,
@StringRes val titleRes: Int,
@StringRes val contentDescriptionRes: Int
) : Parcelable {
STANDARD(
preferenceKey = R.string.pref_key_tracking_protection_standard_option,
titleRes = R.string.preference_enhanced_tracking_protection_standard_option,
contentDescriptionRes = R.string.preference_enhanced_tracking_protection_standard_info_button
),
STRICT(
preferenceKey = R.string.pref_key_tracking_protection_strict_default,
titleRes = R.string.preference_enhanced_tracking_protection_strict,
contentDescriptionRes = R.string.preference_enhanced_tracking_protection_strict_info_button
),
CUSTOM(
preferenceKey = R.string.pref_key_tracking_protection_custom_option,
titleRes = R.string.preference_enhanced_tracking_protection_custom,
contentDescriptionRes = R.string.preference_enhanced_tracking_protection_custom_info_button
)
}

View File

@ -57,6 +57,16 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
private lateinit var trackingProtectionStore: TrackingProtectionStore
private lateinit var trackingProtectionView: TrackingProtectionPanelView
private lateinit var trackingProtectionInteractor: TrackingProtectionPanelInteractor
private lateinit var trackingProtectionUseCases: TrackingProtectionUseCases
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val components = requireComponents
trackingProtectionUseCases = TrackingProtectionUseCases(
sessionManager = components.core.sessionManager,
engine = components.core.engine
)
}
override fun onCreateView(
inflater: LayoutInflater,
@ -85,47 +95,34 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
)
trackingProtectionView =
TrackingProtectionPanelView(view.fragment_tp, trackingProtectionInteractor)
updateTrackers()
session?.let { updateTrackers(it) }
return view
}
private val sessionObserver = object : Session.Observer {
override fun onUrlChanged(session: Session, url: String) {
trackingProtectionStore.dispatch(
TrackingProtectionAction.UrlChange(url)
)
trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange(url))
}
override fun onTrackerBlocked(session: Session, tracker: Tracker, all: List<Tracker>) {
updateTrackers()
updateTrackers(session)
}
override fun onTrackerLoaded(session: Session, tracker: Tracker, all: List<Tracker>) {
updateTrackers()
updateTrackers(session)
}
}
private fun updateTrackers() {
context?.let { context ->
val session =
context.components.core.sessionManager.findSessionById(args.sessionId) ?: return
val useCase = TrackingProtectionUseCases(
sessionManager = context.components.core.sessionManager,
engine = context.components.core.engine
)
useCase.fetchTrackingLogs(
session,
onSuccess = {
trackingProtectionStore.dispatch(
TrackingProtectionAction.TrackerLogChange(it)
)
},
onError = {
Logger.error("TrackingProtectionUseCases - fetchTrackingLogs onError", it)
}
)
}
private fun updateTrackers(session: Session) {
trackingProtectionUseCases.fetchTrackingLogs(
session,
onSuccess = {
trackingProtectionStore.dispatch(TrackingProtectionAction.TrackerLogChange(it))
},
onError = {
Logger.error("TrackingProtectionUseCases - fetchTrackingLogs onError", it)
}
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -150,17 +147,13 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
private fun toggleTrackingProtection(isEnabled: Boolean) {
context?.let { context ->
val useCase = TrackingProtectionUseCases(
sessionManager = context.components.core.sessionManager,
engine = context.components.core.engine
)
val session = context.components.core.sessionManager.findSessionById(args.sessionId)
session?.let {
if (isEnabled) {
useCase.removeException(it)
trackingProtectionUseCases.removeException(it)
} else {
context.metrics.track(Event.TrackingProtectionException)
useCase.addException(it)
trackingProtectionUseCases.addException(it)
}
with(context.components) {

View File

@ -77,11 +77,18 @@ class TrackingProtectionPanelView(
private var shouldFocusAccessibilityView: Boolean = true
fun update(state: TrackingProtectionState) {
if (state.mode != mode) {
mode = state.mode
init {
protection_settings.setOnClickListener {
interactor.selectTrackingProtectionSettings()
}
details_back.setOnClickListener {
interactor.onBackPressed()
}
setCategoryClickListeners()
}
fun update(state: TrackingProtectionState) {
mode = state.mode
bucketedTrackers.updateIfNeeded(state.listTrackers)
when (val mode = state.mode) {
@ -103,16 +110,31 @@ class TrackingProtectionPanelView(
not_blocking_header.isGone = bucketedTrackers.loadedIsEmpty()
bindUrl(state.url)
bindTrackingProtectionInfo(state.isTrackingProtectionEnabled)
protection_settings.setOnClickListener {
interactor.selectTrackingProtectionSettings()
}
blocking_header.isGone = bucketedTrackers.blockedIsEmpty()
updateCategoryVisibility()
setCategoryClickListeners()
focusAccessibilityLastUsedCategory(state.lastAccessedCategory)
}
private fun setUIForDetailsMode(
category: TrackingProtectionCategory,
categoryBlocked: Boolean
) {
normal_mode.visibility = View.GONE
details_mode.visibility = View.VISIBLE
category_title.setText(category.title)
blocking_text_list.text = bucketedTrackers.get(category, categoryBlocked).joinToString("\n")
category_description.setText(category.description)
details_blocking_header.setText(if (categoryBlocked) {
R.string.enhanced_tracking_protection_blocked
} else {
R.string.enhanced_tracking_protection_allowed
})
details_back.requestFocus()
details_back.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
/**
* Will force accessibility focus to last entered details category.
* Called when user returns from details_mode.
@ -180,32 +202,6 @@ class TrackingProtectionPanelView(
interactor.openDetails(category, categoryBlocked = !isLoaded(v))
}
private fun setUIForDetailsMode(
category: TrackingProtectionCategory,
categoryBlocked: Boolean
) {
val context = view.context
normal_mode.visibility = View.GONE
details_mode.visibility = View.VISIBLE
category_title.text = context.getString(category.title)
blocking_text_list.text = bucketedTrackers.get(category, categoryBlocked).joinToString("\n")
category_description.text = context.getString(category.description)
details_blocking_header.text = context.getString(
if (categoryBlocked) {
R.string.enhanced_tracking_protection_blocked
} else {
R.string.enhanced_tracking_protection_allowed
}
)
details_back.setOnClickListener {
interactor.onBackPressed()
}
details_back.requestFocus()
details_back.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED)
}
private fun bindUrl(url: String) {
this.url.text = url.toUri().hostWithoutCommonPrefixes
}
@ -239,9 +235,9 @@ class TrackingProtectionPanelView(
ViewCompat.setAccessibilityDelegate(view2, object : AccessibilityDelegateCompat() {
override fun onInitializeAccessibilityNodeInfo(
host: View?,
info: AccessibilityNodeInfoCompat?
info: AccessibilityNodeInfoCompat
) {
info?.setTraversalAfter(view1)
info.setTraversalAfter(view1)
super.onInitializeAccessibilityNodeInfo(host, info)
}
})

View File

@ -4,6 +4,7 @@
package org.mozilla.fenix.trackingprotection
import androidx.annotation.StringRes
import mozilla.components.browser.session.Session
import mozilla.components.concept.engine.content.blocking.TrackerLog
import mozilla.components.lib.state.Action
@ -73,7 +74,10 @@ data class TrackingProtectionState(
/**
* The 5 categories of Tracking Protection to display
*/
enum class TrackingProtectionCategory(val title: Int, val description: Int) {
enum class TrackingProtectionCategory(
@StringRes val title: Int,
@StringRes val description: Int
) {
SOCIAL_MEDIA_TRACKERS(
R.string.etp_social_media_trackers_title,
R.string.etp_social_media_trackers_description

View File

@ -188,7 +188,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:tint="?attr/primaryText"
app:tint="?attr/primaryText"
android:contentDescription="@string/etp_back_button_content_description"
app:layout_constraintBottom_toBottomOf="@+id/category_description"
app:layout_constraintStart_toStartOf="parent"

View File

@ -696,10 +696,11 @@
</dialog>
<fragment
android:id="@+id/trackingProtectionBlockingFragment"
android:name="org.mozilla.fenix.trackingprotection.TrackingProtectionBlockingFragment">
android:name="org.mozilla.fenix.trackingprotection.TrackingProtectionBlockingFragment"
tools:layout="@layout/fragment_tracking_protection_blocking">
<argument
android:name="protectionMode"
app:argType="string" />
app:argType="org.mozilla.fenix.trackingprotection.TrackingProtectionMode" />
</fragment>
<fragment
android:id="@+id/deleteBrowsingDataOnQuitFragment"

View File

@ -0,0 +1,100 @@
/* 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.trackingprotection
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.isVisible
import io.mockk.mockk
import io.mockk.verify
import kotlinx.android.synthetic.main.component_tracking_protection_panel.*
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS
@RunWith(FenixRobolectricTestRunner::class)
class TrackingProtectionPanelViewTest {
private lateinit var container: ViewGroup
private lateinit var interactor: TrackingProtectionPanelInteractor
private lateinit var view: TrackingProtectionPanelView
private val baseState = TrackingProtectionState(
session = null,
url = "",
isTrackingProtectionEnabled = false,
listTrackers = emptyList(),
mode = TrackingProtectionState.Mode.Normal,
lastAccessedCategory = ""
)
@Before
fun setup() {
container = FrameLayout(testContext)
interactor = mockk(relaxUnitFun = true)
view = TrackingProtectionPanelView(container, interactor)
}
@Test
fun testNormalModeUi() {
view.update(baseState.copy(mode = TrackingProtectionState.Mode.Normal))
assertFalse(view.details_mode.isVisible)
assertTrue(view.normal_mode.isVisible)
assertTrue(view.protection_settings.isVisible)
assertFalse(view.not_blocking_header.isVisible)
assertFalse(view.blocking_header.isVisible)
}
@Test
fun testPrivateModeUi() {
view.update(baseState.copy(
mode = TrackingProtectionState.Mode.Details(
selectedCategory = TrackingProtectionCategory.TRACKING_CONTENT,
categoryBlocked = false
)
))
assertTrue(view.details_mode.isVisible)
assertFalse(view.normal_mode.isVisible)
assertEquals(
testContext.getString(R.string.etp_tracking_content_title),
view.category_title.text
)
assertEquals(
testContext.getString(R.string.etp_tracking_content_description),
view.category_description.text
)
assertEquals(
testContext.getString(R.string.enhanced_tracking_protection_allowed),
view.details_blocking_header.text
)
}
@Test
fun testProtectionSettings() {
view.protection_settings.performClick()
verify { interactor.selectTrackingProtectionSettings() }
}
@Test
fun testDetailsBack() {
view.details_back.performClick()
verify { interactor.onBackPressed() }
}
@Test
fun testSocialMediaTrackerClick() {
view.social_media_trackers.performClick()
verify { interactor.openDetails(SOCIAL_MEDIA_TRACKERS, categoryBlocked = true) }
view.social_media_trackers_loaded.performClick()
verify { interactor.openDetails(SOCIAL_MEDIA_TRACKERS, categoryBlocked = false) }
}
}