[fenix] For https://github.com/mozilla-mobile/fenix/issues/20890 when TP is off globally hide TP section on quick settings.

pull/600/head
Arturo Mejia 3 years ago committed by mergify[bot]
parent 1928220c0d
commit a82a02a9e7

@ -11,19 +11,30 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import mozilla.components.browser.state.selector.findTabOrCustomTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.ext.consumeFlow
import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.lib.state.ext.consumeFrom
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.android.FenixDialogFragment import org.mozilla.fenix.android.FenixDialogFragment
import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.PhoneFeature
/** /**
@ -38,7 +49,10 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
private lateinit var quickSettingsController: QuickSettingsController private lateinit var quickSettingsController: QuickSettingsController
private lateinit var websiteInfoView: WebsiteInfoView private lateinit var websiteInfoView: WebsiteInfoView
private lateinit var websitePermissionsView: WebsitePermissionsView private lateinit var websitePermissionsView: WebsitePermissionsView
private lateinit var trackingProtectionView: TrackingProtectionView
@VisibleForTesting
internal lateinit var trackingProtectionView: TrackingProtectionView
private lateinit var interactor: QuickSettingsInteractor private lateinit var interactor: QuickSettingsInteractor
private var tryToRequestPermissions: Boolean = false private var tryToRequestPermissions: Boolean = false
@ -101,7 +115,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
websitePermissionsView = websitePermissionsView =
WebsitePermissionsView(binding.websitePermissionsLayout, interactor) WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
trackingProtectionView = trackingProtectionView =
TrackingProtectionView(binding.trackingProtectionLayout, interactor) TrackingProtectionView(binding.trackingProtectionLayout, interactor, context.settings())
return rootView return rootView
} }
@ -109,7 +123,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
observeTrackersChange(requireComponents.core.store)
consumeFrom(quickSettingsStore) { consumeFrom(quickSettingsStore) {
websiteInfoView.update(it.webInfoState) websiteInfoView.update(it.webInfoState)
websitePermissionsView.update(it.websitePermissionsState) websitePermissionsView.update(it.websitePermissionsState)
@ -157,6 +171,42 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
) )
} }
@VisibleForTesting
internal fun provideTabId(): String = args.sessionId
@VisibleForTesting
@ExperimentalCoroutinesApi
internal fun observeTrackersChange(store: BrowserStore) {
consumeFlow(store) { flow ->
flow.mapNotNull { state ->
state.findTabOrCustomTab(provideTabId())
}.ifAnyChanged { tab ->
arrayOf(
tab.trackingProtection.blockedTrackers,
tab.trackingProtection.loadedTrackers
)
}.collect {
updateTrackers(it)
}
}
}
@VisibleForTesting
internal fun updateTrackers(tab: SessionState) {
provideTrackingProtectionUseCases().fetchTrackingLogs(
tab.id,
onSuccess = { trackers ->
trackingProtectionView.updateDetailsSection(trackers.isNotEmpty())
},
onError = {
Logger.error("QuickSettingsSheetDialogFragment - fetchTrackingLogs onError", it)
}
)
}
@VisibleForTesting
internal fun provideTrackingProtectionUseCases() = requireComponents.useCases.trackingProtectionUseCases
private companion object { private companion object {
const val REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS = 4 const val REQUEST_CODE_QUICK_SETTINGS_PERMISSIONS = 4
} }

@ -6,9 +6,13 @@ package org.mozilla.fenix.settings.quicksettings
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.QuicksettingsTrackingProtectionBinding import org.mozilla.fenix.databinding.QuicksettingsTrackingProtectionBinding
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.trackingprotection.TrackingProtectionState import org.mozilla.fenix.trackingprotection.TrackingProtectionState
import org.mozilla.fenix.utils.Settings
/** /**
* Contract declaring all possible user interactions with [TrackingProtectionView]. * Contract declaring all possible user interactions with [TrackingProtectionView].
@ -35,26 +39,33 @@ interface TrackingProtectionInteractor {
* *
* @param containerView [ViewGroup] in which this View will inflate itself. * @param containerView [ViewGroup] in which this View will inflate itself.
* @param interactor [TrackingProtectionInteractor] which will have delegated to all user * @param interactor [TrackingProtectionInteractor] which will have delegated to all user
* @param settings [Settings] application settings.
* interactions. * interactions.
*/ */
class TrackingProtectionView( class TrackingProtectionView(
val containerView: ViewGroup, val containerView: ViewGroup,
val interactor: TrackingProtectionInteractor, val interactor: TrackingProtectionInteractor,
val settings: Settings
) { ) {
private val context = containerView.context private val context = containerView.context
private val binding = QuicksettingsTrackingProtectionBinding.inflate( @VisibleForTesting
internal val binding = QuicksettingsTrackingProtectionBinding.inflate(
LayoutInflater.from(containerView.context), LayoutInflater.from(containerView.context),
containerView, containerView,
true true
) )
fun update(state: TrackingProtectionState) { fun update(state: TrackingProtectionState) {
bindTrackingProtectionInfo(state.isTrackingProtectionEnabled) bindTrackingProtectionInfo(state.isTrackingProtectionEnabled)
binding.root.isVisible = settings.shouldUseTrackingProtection
binding.trackingProtectionDetails.setOnClickListener { binding.trackingProtectionDetails.setOnClickListener {
interactor.onDetailsClicked() interactor.onDetailsClicked()
} }
} }
fun updateDetailsSection(show: Boolean) {
binding.trackingProtectionDetails.isVisible = show
}
private fun bindTrackingProtectionInfo(isTrackingProtectionEnabled: Boolean) { private fun bindTrackingProtectionInfo(isTrackingProtectionEnabled: Boolean) {
binding.trackingProtectionSwitch.trackingProtectionCategoryItemDescription.text = binding.trackingProtectionSwitch.trackingProtectionCategoryItemDescription.text =
context.getString(if (isTrackingProtectionEnabled) R.string.etp_panel_on else R.string.etp_panel_off) context.getString(if (isTrackingProtectionEnabled) R.string.etp_panel_on else R.string.etp_panel_off)

@ -82,7 +82,7 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
): View { ): View {
val store = requireComponents.core.store val store = requireComponents.core.store
val view = inflateRootView(container) val view = inflateRootView(container)
val tab = store.state.findTabOrCustomTab(provideTabId()) val tab = store.state.findTabOrCustomTab(provideCurrentTabId())
trackingProtectionStore = StoreProvider.get(this) { trackingProtectionStore = StoreProvider.get(this) {
TrackingProtectionStore( TrackingProtectionStore(
@ -201,7 +201,7 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
internal fun observeUrlChange(store: BrowserStore) { internal fun observeUrlChange(store: BrowserStore) {
consumeFlow(store) { flow -> consumeFlow(store) { flow ->
flow.mapNotNull { state -> flow.mapNotNull { state ->
state.findTabOrCustomTab(provideTabId()) state.findTabOrCustomTab(provideCurrentTabId())
}.ifChanged { tab -> tab.content.url } }.ifChanged { tab -> tab.content.url }
.collect { .collect {
trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange(it.content.url)) trackingProtectionStore.dispatch(TrackingProtectionAction.UrlChange(it.content.url))
@ -210,13 +210,13 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt
} }
@VisibleForTesting @VisibleForTesting
internal fun provideTabId(): String = args.sessionId internal fun provideCurrentTabId(): String = args.sessionId
@VisibleForTesting @VisibleForTesting
internal fun observeTrackersChange(store: BrowserStore) { internal fun observeTrackersChange(store: BrowserStore) {
consumeFlow(store) { flow -> consumeFlow(store) { flow ->
flow.mapNotNull { state -> flow.mapNotNull { state ->
state.findTabOrCustomTab(provideTabId()) state.findTabOrCustomTab(provideCurrentTabId())
}.ifAnyChanged { tab -> }.ifAnyChanged { tab ->
arrayOf( arrayOf(
tab.trackingProtection.blockedTrackers, tab.trackingProtection.blockedTrackers,

@ -29,6 +29,7 @@
android:gravity="end|center_vertical" android:gravity="end|center_vertical"
android:layout_alignParentEnd="true" android:layout_alignParentEnd="true"
android:text="@string/enhanced_tracking_protection_details" android:text="@string/enhanced_tracking_protection_details"
android:visibility="gone"
app:drawableEndCompat="@drawable/ic_arrowhead_right" app:drawableEndCompat="@drawable/ic_arrowhead_right"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

@ -0,0 +1,182 @@
/* 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 androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import io.mockk.every
import io.mockk.mockk
import io.mockk.slot
import io.mockk.spyk
import io.mockk.verify
import junit.framework.TestCase.assertNotSame
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import mozilla.components.browser.state.action.TabListAction
import mozilla.components.browser.state.action.TrackingProtectionAction.TrackerBlockedAction
import mozilla.components.browser.state.action.TrackingProtectionAction.TrackerLoadedAction
import mozilla.components.browser.state.selector.findTab
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.content.blocking.TrackerLog
import mozilla.components.feature.session.TrackingProtectionUseCases
import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.rule.MainCoroutineRule
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@ExperimentalCoroutinesApi
@RunWith(FenixRobolectricTestRunner::class)
class QuickSettingsSheetDialogFragmentTest {
private val testDispatcher = TestCoroutineDispatcher()
@get:Rule
val coroutinesTestRule = MainCoroutineRule(testDispatcher)
private lateinit var lifecycleOwner: MockedLifecycleOwner
private lateinit var fragment: QuickSettingsSheetDialogFragment
private lateinit var store: BrowserStore
@Before
fun setup() {
fragment = spyk(QuickSettingsSheetDialogFragment())
lifecycleOwner = MockedLifecycleOwner(Lifecycle.State.STARTED)
store = BrowserStore()
every { fragment.view } returns mockk(relaxed = true)
every { fragment.lifecycle } returns lifecycleOwner.lifecycle
every { fragment.activity } returns mockk(relaxed = true)
}
@After
fun cleanUp() {
testDispatcher.cleanupTestCoroutines()
}
@Test
fun `WHEN a tracker is loaded THEN trackers view is updated`() {
val tab = createTab("mozilla.org")
every { fragment.provideTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store)
addAndSelectTab(tab)
verify(exactly = 1) {
fragment.updateTrackers(tab)
}
store.dispatch(TrackerLoadedAction(tab.id, mockk())).joinBlocking()
val updatedTab = store.state.findTab(tab.id)!!
assertNotSame(updatedTab, tab)
verify(exactly = 1) {
fragment.updateTrackers(updatedTab)
}
}
@Test
fun `WHEN a tracker is blocked THEN trackers view is updated`() {
val tab = createTab("mozilla.org")
every { fragment.provideTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store)
addAndSelectTab(tab)
verify(exactly = 1) {
fragment.updateTrackers(tab)
}
store.dispatch(TrackerBlockedAction(tab.id, mockk())).joinBlocking()
val updatedTab = store.state.findTab(tab.id)!!
assertNotSame(updatedTab, tab)
verify(exactly = 1) {
fragment.updateTrackers(updatedTab)
}
}
@Test
fun `GIVEN no trackers WHEN calling updateTrackers THEN hide the details section`() {
val tab = createTab("mozilla.org")
val trackingProtectionUseCases: TrackingProtectionUseCases = mockk(relaxed = true)
val trackingProtectionView: TrackingProtectionView = mockk(relaxed = true)
val onComplete = slot<(List<TrackerLog>) -> Unit>()
every { fragment.trackingProtectionView } returns trackingProtectionView
every {
trackingProtectionUseCases.fetchTrackingLogs.invoke(
any(),
capture(onComplete),
any()
)
}.answers { onComplete.captured.invoke(emptyList()) }
every { fragment.provideTrackingProtectionUseCases() } returns trackingProtectionUseCases
fragment.updateTrackers(tab)
verify {
trackingProtectionView.updateDetailsSection(false)
}
}
@Test
fun `GIVEN trackers WHEN calling updateTrackers THEN show the details section`() {
val tab = createTab("mozilla.org")
val trackingProtectionUseCases: TrackingProtectionUseCases = mockk(relaxed = true)
val trackingProtectionView: TrackingProtectionView = mockk(relaxed = true)
val onComplete = slot<(List<TrackerLog>) -> Unit>()
every { fragment.trackingProtectionView } returns trackingProtectionView
every {
trackingProtectionUseCases.fetchTrackingLogs.invoke(
any(),
capture(onComplete),
any()
)
}.answers { onComplete.captured.invoke(listOf(TrackerLog(""))) }
every { fragment.provideTrackingProtectionUseCases() } returns trackingProtectionUseCases
fragment.updateTrackers(tab)
verify {
trackingProtectionView.updateDetailsSection(true)
}
}
private fun addAndSelectTab(tab: TabSessionState) {
store.dispatch(TabListAction.AddTabAction(tab)).joinBlocking()
store.dispatch(TabListAction.SelectTabAction(tab.id)).joinBlocking()
}
internal class MockedLifecycleOwner(initialState: Lifecycle.State) : LifecycleOwner {
private val lifecycleRegistry = LifecycleRegistry(this).apply {
currentState = initialState
}
override fun getLifecycle(): Lifecycle = lifecycleRegistry
}
}

@ -0,0 +1,95 @@
/* 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 android.widget.FrameLayout
import androidx.core.view.isVisible
import io.mockk.MockKAnnotations
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.mockk
import io.mockk.spyk
import mozilla.components.browser.state.state.createTab
import mozilla.components.support.test.robolectric.testContext
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.databinding.QuicksettingsTrackingProtectionBinding
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
import org.mozilla.fenix.utils.Settings
@RunWith(FenixRobolectricTestRunner::class)
class TrackingProtectionViewTest {
private lateinit var view: TrackingProtectionView
private lateinit var binding: QuicksettingsTrackingProtectionBinding
private lateinit var interactor: TrackingProtectionInteractor
@MockK(relaxed = true)
private lateinit var settings: Settings
@Before
fun setup() {
MockKAnnotations.init(this)
interactor = mockk(relaxed = true)
view = spyk(TrackingProtectionView(FrameLayout(testContext), interactor, settings))
binding = view.binding
}
@Test
fun `WHEN updating THEN bind checkbox`() {
val websiteUrl = "https://mozilla.org"
val state = TrackingProtectionState(
tab = createTab(url = websiteUrl),
url = websiteUrl,
isTrackingProtectionEnabled = true,
listTrackers = listOf(),
mode = TrackingProtectionState.Mode.Normal,
lastAccessedCategory = ""
)
every { settings.shouldUseTrackingProtection } returns true
view.update(state)
assertTrue(binding.root.isVisible)
assertTrue(binding.trackingProtectionSwitch.switchWidget.isChecked)
}
@Test
fun `GIVEN TP is globally off WHEN updating THEN hide the TP section`() {
val websiteUrl = "https://mozilla.org"
val state = TrackingProtectionState(
tab = createTab(url = websiteUrl),
url = websiteUrl,
isTrackingProtectionEnabled = true,
listTrackers = listOf(),
mode = TrackingProtectionState.Mode.Normal,
lastAccessedCategory = ""
)
every { settings.shouldUseTrackingProtection } returns false
view.update(state)
assertFalse(binding.root.isVisible)
}
@Test
fun `WHEN updateDetailsSection is called THEN update the visibility of the section`() {
every { settings.shouldUseTrackingProtection } returns false
view.updateDetailsSection(false)
assertFalse(binding.trackingProtectionDetails.isVisible)
view.updateDetailsSection(true)
assertTrue(binding.trackingProtectionDetails.isVisible)
}
}

@ -65,7 +65,7 @@ class TrackingProtectionPanelDialogFragmentTest {
val tab = createTab("mozilla.org") val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id every { fragment.provideCurrentTabId() } returns tab.id
fragment.observeUrlChange(store) fragment.observeUrlChange(store)
addAndSelectTab(tab) addAndSelectTab(tab)
@ -87,7 +87,7 @@ class TrackingProtectionPanelDialogFragmentTest {
val tab = createTab("mozilla.org") val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id every { fragment.provideCurrentTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store) fragment.observeTrackersChange(store)
@ -114,7 +114,7 @@ class TrackingProtectionPanelDialogFragmentTest {
val tab = createTab("mozilla.org") val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id every { fragment.provideCurrentTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store) fragment.observeTrackersChange(store)

Loading…
Cancel
Save