For #20890 when TP is off globally hide TP section on quick settings.

upstream-sync
Arturo Mejia 3 years ago committed by mergify[bot]
parent 6855295b61
commit 8b4190a429

@ -11,19 +11,30 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.mapNotNull
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.support.base.log.logger.Logger
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R
import org.mozilla.fenix.android.FenixDialogFragment
import org.mozilla.fenix.databinding.FragmentQuickSettingsDialogSheetBinding
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.settings.PhoneFeature
/**
@ -38,7 +49,10 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
private lateinit var quickSettingsController: QuickSettingsController
private lateinit var websiteInfoView: WebsiteInfoView
private lateinit var websitePermissionsView: WebsitePermissionsView
private lateinit var trackingProtectionView: TrackingProtectionView
@VisibleForTesting
internal lateinit var trackingProtectionView: TrackingProtectionView
private lateinit var interactor: QuickSettingsInteractor
private var tryToRequestPermissions: Boolean = false
@ -101,7 +115,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
websitePermissionsView =
WebsitePermissionsView(binding.websitePermissionsLayout, interactor)
trackingProtectionView =
TrackingProtectionView(binding.trackingProtectionLayout, interactor)
TrackingProtectionView(binding.trackingProtectionLayout, interactor, context.settings())
return rootView
}
@ -109,7 +123,7 @@ class QuickSettingsSheetDialogFragment : FenixDialogFragment() {
@ExperimentalCoroutinesApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
observeTrackersChange(requireComponents.core.store)
consumeFrom(quickSettingsStore) {
websiteInfoView.update(it.webInfoState)
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 {
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.ViewGroup
import androidx.annotation.VisibleForTesting
import androidx.core.view.isVisible
import org.mozilla.fenix.R
import org.mozilla.fenix.databinding.QuicksettingsTrackingProtectionBinding
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.trackingprotection.TrackingProtectionState
import org.mozilla.fenix.utils.Settings
/**
* 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 interactor [TrackingProtectionInteractor] which will have delegated to all user
* @param settings [Settings] application settings.
* interactions.
*/
class TrackingProtectionView(
val containerView: ViewGroup,
val interactor: TrackingProtectionInteractor,
val settings: Settings
) {
private val context = containerView.context
private val binding = QuicksettingsTrackingProtectionBinding.inflate(
@VisibleForTesting
internal val binding = QuicksettingsTrackingProtectionBinding.inflate(
LayoutInflater.from(containerView.context),
containerView,
true
)
fun update(state: TrackingProtectionState) {
bindTrackingProtectionInfo(state.isTrackingProtectionEnabled)
binding.root.isVisible = settings.shouldUseTrackingProtection
binding.trackingProtectionDetails.setOnClickListener {
interactor.onDetailsClicked()
}
}
fun updateDetailsSection(show: Boolean) {
binding.trackingProtectionDetails.isVisible = show
}
private fun bindTrackingProtectionInfo(isTrackingProtectionEnabled: Boolean) {
binding.trackingProtectionSwitch.trackingProtectionCategoryItemDescription.text =
context.getString(if (isTrackingProtectionEnabled) R.string.etp_panel_on else R.string.etp_panel_off)

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

@ -29,6 +29,7 @@
android:gravity="end|center_vertical"
android:layout_alignParentEnd="true"
android:text="@string/enhanced_tracking_protection_details"
android:visibility="gone"
app:drawableEndCompat="@drawable/ic_arrowhead_right"
app:layout_constraintBottom_toBottomOf="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")
every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id
every { fragment.provideCurrentTabId() } returns tab.id
fragment.observeUrlChange(store)
addAndSelectTab(tab)
@ -87,7 +87,7 @@ class TrackingProtectionPanelDialogFragmentTest {
val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id
every { fragment.provideCurrentTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store)
@ -114,7 +114,7 @@ class TrackingProtectionPanelDialogFragmentTest {
val tab = createTab("mozilla.org")
every { fragment.trackingProtectionStore } returns trackingProtectionStore
every { fragment.provideTabId() } returns tab.id
every { fragment.provideCurrentTabId() } returns tab.id
every { fragment.updateTrackers(any()) } returns Unit
fragment.observeTrackersChange(store)

Loading…
Cancel
Save