diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionDetailsController.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionDetailsController.kt index 58c6f4fc8..4d3b690aa 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionDetailsController.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionDetailsController.kt @@ -28,8 +28,6 @@ interface ConnectionDetailsController { /** * Default behavior of [ConnectionDetailsController]. - * - * @param dismiss callback allowing to request this entire Fragment to be dismissed. */ class DefaultConnectionDetailsController( private val context: Context, @@ -37,9 +35,9 @@ class DefaultConnectionDetailsController( private val navController: () -> NavController, internal var sitePermissions: SitePermissions?, private val gravity: Int, - private val getCurrentTab: () -> SessionState?, - private val dismiss: () -> Unit + private val getCurrentTab: () -> SessionState? ) : ConnectionDetailsController { + override fun handleBackPressed() { getCurrentTab()?.let { tab -> context.components.useCases.trackingProtectionUseCases.containsException(tab.id) { contains -> diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionPanelDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionPanelDialogFragment.kt index b98c8df42..b0421d031 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionPanelDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/ConnectionPanelDialogFragment.kt @@ -34,15 +34,16 @@ class ConnectionPanelDialogFragment : FenixDialogFragment() { savedInstanceState: Bundle? ): View { val rootView = inflateRootView(container) + val controller = DefaultConnectionDetailsController( context = requireContext(), fragment = this, navController = { findNavController() }, sitePermissions = args.sitePermissions, gravity = args.gravity, - getCurrentTab = ::getCurrentTab, - dismiss = ::dismiss + getCurrentTab = ::getCurrentTab ) + val interactor = ConnectionDetailsInteractor(controller) connectionView = WebsiteInfoView( container = rootView.connectionDetailsInfoLayout, @@ -65,7 +66,7 @@ class ConnectionPanelDialogFragment : FenixDialogFragment() { ) } - internal fun getCurrentTab(): SessionState? { + private fun getCurrentTab(): SessionState? { return requireComponents.core.store.state.findTabOrCustomTab(args.sessionId) } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt index a35a673a0..d32c449aa 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsController.kt @@ -205,7 +205,8 @@ class DefaultQuickSettingsController( sessionId = sessionId, url = state.url, trackingProtectionEnabled = state.isTrackingProtectionEnabled, - gravity = context.components.settings.toolbarPosition.androidGravity + gravity = context.components.settings.toolbarPosition.androidGravity, + sitePermissions = sitePermissions ) navController().navigate(directions) } diff --git a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsFragmentStore.kt b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsFragmentStore.kt index cc2464d0c..40606e34c 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsFragmentStore.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/quicksettings/QuickSettingsFragmentStore.kt @@ -53,7 +53,7 @@ class QuickSettingsFragmentStore( * @param permissions [SitePermissions]? list of website permissions and their status. * @param settings [Settings] application settings. * @param certificateName [String] the certificate name of the current web page. - * @param sessionId [String] TODO + * @param sessionId [String] The current session ID. * @param isTrackingProtectionEnabled [Boolean] Current status of tracking protection * for this session. */ @@ -127,7 +127,7 @@ class QuickSettingsFragmentStore( * [TrackingProtectionView]. * * @param context [Context] used for various Android interactions. - * @param sessionId [String] TODO + * @param sessionId [String] The current session ID. * @param websiteUrl [String] the URL of the current web page. * @param isTrackingProtectionEnabled [Boolean] Current status of tracking protection * for this session. diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt index 0c29fda19..7b3cdd250 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelDialogFragment.kt @@ -19,6 +19,7 @@ import androidx.appcompat.app.AppCompatDialogFragment import androidx.appcompat.view.ContextThemeWrapper import androidx.lifecycle.lifecycleScope import androidx.lifecycle.whenStarted +import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -96,8 +97,14 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt ) } trackingProtectionInteractor = TrackingProtectionPanelInteractor( - trackingProtectionStore, - ::openTrackingProtectionSettings + context = requireContext(), + fragment = this, + store = trackingProtectionStore, + navController = { findNavController() }, + openTrackingProtectionSettings = ::openTrackingProtectionSettings, + sitePermissions = args.sitePermissions, + gravity = args.gravity, + getCurrentTab = ::getCurrentTab ) trackingProtectionView = TrackingProtectionPanelView(view.fragment_tp, trackingProtectionInteractor) @@ -219,4 +226,8 @@ class TrackingProtectionPanelDialogFragment : AppCompatDialogFragment(), UserInt } } } + + private fun getCurrentTab(): SessionState? { + return requireComponents.core.store.state.findTabOrCustomTab(args.sessionId) + } } diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt index a7b65976a..02cbe9728 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractor.kt @@ -4,14 +4,31 @@ package org.mozilla.fenix.trackingprotection +import android.content.Context +import androidx.fragment.app.Fragment +import androidx.navigation.NavController +import mozilla.components.browser.state.state.SessionState +import mozilla.components.concept.engine.permission.SitePermissions +import org.mozilla.fenix.browser.BrowserFragmentDirections +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.ext.runIfFragmentIsAttached + /** * Interactor for the tracking protection panel * Provides implementations for the TrackingProtectionPanelViewInteractor */ +@Suppress("LongParameterList") class TrackingProtectionPanelInteractor( + private val context: Context, + private val fragment: Fragment, private val store: TrackingProtectionStore, - private val openTrackingProtectionSettings: () -> Unit + private val navController: () -> NavController, + private val openTrackingProtectionSettings: () -> Unit, + internal var sitePermissions: SitePermissions?, + private val gravity: Int, + private val getCurrentTab: () -> SessionState? ) : TrackingProtectionPanelViewInteractor { + override fun openDetails(category: TrackingProtectionCategory, categoryBlocked: Boolean) { store.dispatch(TrackingProtectionAction.EnterDetailsMode(category, categoryBlocked)) } @@ -21,6 +38,30 @@ class TrackingProtectionPanelInteractor( } override fun onBackPressed() { + getCurrentTab()?.let { tab -> + context.components.useCases.trackingProtectionUseCases.containsException(tab.id) { contains -> + fragment.runIfFragmentIsAttached { + navController().popBackStack() + val isTrackingProtectionEnabled = tab.trackingProtection.enabled && !contains + val directions = + BrowserFragmentDirections.actionGlobalQuickSettingsSheetDialogFragment( + sessionId = tab.id, + url = tab.content.url, + title = tab.content.title, + isSecured = tab.content.securityInfo.secure, + sitePermissions = sitePermissions, + gravity = gravity, + certificateName = tab.content.securityInfo.issuer, + permissionHighlights = tab.content.permissionHighlights, + isTrackingProtectionEnabled = isTrackingProtectionEnabled + ) + navController().navigate(directions) + } + } + } + } + + override fun onExitDetailMode() { store.dispatch(TrackingProtectionAction.ExitDetailsMode) } } diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt index 437064f5a..eeb39acd3 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelView.kt @@ -9,7 +9,6 @@ import android.view.View import android.view.ViewGroup import android.view.accessibility.AccessibilityEvent import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.net.toUri import androidx.core.view.AccessibilityDelegateCompat import androidx.core.view.ViewCompat import androidx.core.view.accessibility.AccessibilityNodeInfoCompat @@ -17,18 +16,16 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import kotlinx.android.extensions.LayoutContainer import kotlinx.android.synthetic.main.component_tracking_protection_panel.* -import kotlinx.android.synthetic.main.component_tracking_protection_panel.details_blocking_header import mozilla.components.browser.state.state.CustomTabSessionState -import mozilla.components.support.ktx.android.net.hostWithoutCommonPrefixes import org.mozilla.fenix.R import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.metrics import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CROSS_SITE_TRACKING_COOKIES import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.CRYPTOMINERS import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.FINGERPRINTERS +import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.REDIRECT_TRACKERS import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.SOCIAL_MEDIA_TRACKERS import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.TRACKING_CONTENT -import org.mozilla.fenix.trackingprotection.TrackingProtectionCategory.REDIRECT_TRACKERS /** * Interface for the TrackingProtectionPanelViewInteractor. This interface is implemented by objects that want @@ -45,6 +42,11 @@ interface TrackingProtectionPanelViewInteractor { */ fun onBackPressed() + /** + * Called whenever back button is pressed in Detail mode. + */ + fun onExitDetailMode() + /** * Called whenever an active tracking protection category is tapped * @param category The Tracking Protection Category to view details about @@ -76,9 +78,15 @@ class TrackingProtectionPanelView( protection_settings.setOnClickListener { interactor.selectTrackingProtectionSettings() } + details_back.setOnClickListener { + interactor.onExitDetailMode() + } + + navigate_back.setOnClickListener { interactor.onBackPressed() } + setCategoryClickListeners() } @@ -100,12 +108,11 @@ class TrackingProtectionPanelView( private fun setUIForNormalMode(state: TrackingProtectionState) { details_mode.visibility = View.GONE normal_mode.visibility = View.VISIBLE - protection_settings.isGone = state.tab is CustomTabSessionState + protection_settings.isGone = state.tab is CustomTabSessionState not_blocking_header.isGone = bucketedTrackers.loadedIsEmpty() - bindUrl(state.url) - blocking_header.isGone = bucketedTrackers.blockedIsEmpty() + updateCategoryVisibility() focusAccessibilityLastUsedCategory(state.lastAccessedCategory) } @@ -213,10 +220,6 @@ class TrackingProtectionPanelView( interactor.openDetails(category, categoryBlocked = !isLoaded(v)) } - private fun bindUrl(url: String) { - this.url.text = url.toUri().hostWithoutCommonPrefixes - } - fun onBackPressed(): Boolean { return when (mode) { is TrackingProtectionState.Mode.Details -> { diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt index 2b2ea0682..4ba568ff4 100644 --- a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionStore.kt @@ -45,14 +45,14 @@ sealed class TrackingProtectionAction : Action { /** * The state for the Tracking Protection Panel - * @property tab TODO + * @property tab Current session to display * @property url Current URL to display * @property isTrackingProtectionEnabled Current status of tracking protection for this session * (ie is an exception) * @property listTrackers Current Tracker Log list of blocked and loaded tracker categories * @property mode Current Mode of TrackingProtection * @property lastAccessedCategory Remembers the last accessed details category, used to move - * accessibly focus after returning from details_mode + * accessibly focus after returning from details_mode */ data class TrackingProtectionState( val tab: SessionState?, diff --git a/app/src/main/res/layout/component_tracking_protection_panel.xml b/app/src/main/res/layout/component_tracking_protection_panel.xml index 7fa2e3590..0f7ab9058 100644 --- a/app/src/main/res/layout/component_tracking_protection_panel.xml +++ b/app/src/main/res/layout/component_tracking_protection_panel.xml @@ -19,14 +19,29 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintTop_toTopOf="parent"> + + + android:text="@string/enhanced_tracking_protection_details" /> diff --git a/app/src/main/res/layout/quicksettings_tracking_protection.xml b/app/src/main/res/layout/quicksettings_tracking_protection.xml index 08c6c6f3d..bbca07096 100644 --- a/app/src/main/res/layout/quicksettings_tracking_protection.xml +++ b/app/src/main/res/layout/quicksettings_tracking_protection.xml @@ -21,8 +21,6 @@ app:switchIcon="@drawable/ic_tracking_protection" app:switchTitle="@string/preference_enhanced_tracking_protection" /> - - + Unit - private lateinit var controller: DefaultConnectionDetailsController private lateinit var tab: TabSessionState @@ -64,8 +60,7 @@ class DefaultConnectionDetailsControllerTest { navController = { navController }, sitePermissions = sitePermissions, gravity = gravity, - getCurrentTab = { tab }, - dismiss = dismiss + getCurrentTab = { tab } ) every { fragment.context } returns context @@ -87,7 +82,7 @@ class DefaultConnectionDetailsControllerTest { verify { navController.popBackStack() - navController.navigateBlockingForAsyncNavGraph(any()) + navController.navigate(any()) } } } diff --git a/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt index 2c37af1d1..cf01a5209 100644 --- a/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt +++ b/app/src/test/java/org/mozilla/fenix/settings/quicksettings/DefaultQuickSettingsControllerTest.kt @@ -6,15 +6,15 @@ package org.mozilla.fenix.settings.quicksettings import androidx.navigation.NavController import androidx.navigation.NavDirections +import io.mockk.MockKAnnotations import io.mockk.Runs import io.mockk.coVerifyOrder import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.just import io.mockk.mockk import io.mockk.spyk import io.mockk.verify -import io.mockk.MockKAnnotations -import io.mockk.impl.annotations.MockK import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineScope import kotlinx.coroutines.test.runBlockingTest @@ -22,9 +22,9 @@ import mozilla.components.browser.state.state.BrowserState import mozilla.components.browser.state.state.TabSessionState import mozilla.components.browser.state.state.createTab import mozilla.components.browser.state.store.BrowserStore -import mozilla.components.feature.session.SessionUseCases import mozilla.components.concept.engine.permission.SitePermissions import mozilla.components.concept.engine.permission.SitePermissions.Status.NO_DECISION +import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.TrackingProtectionUseCases import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.support.test.mock @@ -43,7 +43,6 @@ import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.directionsEq import org.mozilla.fenix.ext.metrics -import org.mozilla.fenix.ext.navigateBlockingForAsyncNavGraph import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.settings.PhoneFeature import org.mozilla.fenix.settings.quicksettings.ext.shouldBeEnabled @@ -376,7 +375,7 @@ class DefaultQuickSettingsControllerTest { verify { navController.popBackStack() - navController.navigateBlockingForAsyncNavGraph(any()) + navController.navigate(any()) } } @@ -400,7 +399,7 @@ class DefaultQuickSettingsControllerTest { verify { navController.popBackStack() - navController.navigateBlockingForAsyncNavGraph(any()) + navController.navigate(any()) } } } diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt index 14c123cdb..f87c860ff 100644 --- a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelInteractorTest.kt @@ -4,19 +4,89 @@ package org.mozilla.fenix.trackingprotection +import android.content.Context +import androidx.fragment.app.Fragment +import androidx.navigation.NavController +import androidx.navigation.NavDirections +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK import io.mockk.mockk +import io.mockk.slot +import io.mockk.spyk import io.mockk.verify -import org.junit.Test - +import mozilla.components.browser.state.state.TabSessionState +import mozilla.components.browser.state.state.createTab +import mozilla.components.concept.engine.permission.SitePermissions +import mozilla.components.feature.session.TrackingProtectionUseCases +import mozilla.components.support.test.robolectric.testContext import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.ext.components +import org.mozilla.fenix.helpers.FenixRobolectricTestRunner +@RunWith(FenixRobolectricTestRunner::class) class TrackingProtectionPanelInteractorTest { + private lateinit var context: Context + + @MockK(relaxed = true) + private lateinit var navController: NavController + + @MockK(relaxed = true) + private lateinit var fragment: Fragment + + @MockK(relaxed = true) + private lateinit var sitePermissions: SitePermissions + + @MockK(relaxed = true) + private lateinit var store: TrackingProtectionStore + + private lateinit var interactor: TrackingProtectionPanelInteractor + + private lateinit var tab: TabSessionState + + private var openSettings = false + private var gravity = 54 + + @Before + fun setup() { + MockKAnnotations.init(this) + + context = spyk(testContext) + tab = createTab("https://mozilla.org") + + interactor = TrackingProtectionPanelInteractor( + context = context, + fragment = fragment, + store = store, + navController = { navController }, + openTrackingProtectionSettings = { openSettings = true }, + sitePermissions = sitePermissions, + gravity = gravity, + getCurrentTab = { tab } + ) + + val trackingProtectionUseCases: TrackingProtectionUseCases = mockk(relaxed = true) + + every { fragment.context } returns context + every { context.components.useCases.trackingProtectionUseCases } returns trackingProtectionUseCases + + val onComplete = slot<(Boolean) -> Unit>() + every { + trackingProtectionUseCases.containsException.invoke( + any(), + capture(onComplete) + ) + }.answers { onComplete.captured.invoke(true) } + } + @Test - fun openDetails() { - val store: TrackingProtectionStore = mockk(relaxed = true) - val interactor = TrackingProtectionPanelInteractor(store, {}, {}) - interactor.openDetails(TrackingProtectionCategory.FINGERPRINTERS, true) + fun `WHEN openDetails is called THEN store should dispatch EnterDetailsMode action with the right category`() { + interactor.openDetails(TrackingProtectionCategory.FINGERPRINTERS, true, {}, {}) + verify { store.dispatch( TrackingProtectionAction.EnterDetailsMode( @@ -25,14 +95,9 @@ class TrackingProtectionPanelInteractorTest { ) ) } - } - @Test - fun openDetailsForRedirectTrackers() { - val store: TrackingProtectionStore = mockk(relaxed = true) - val interactor = - TrackingProtectionPanelInteractor(store, {}, {}) interactor.openDetails(TrackingProtectionCategory.REDIRECT_TRACKERS, true) + verify { store.dispatch( TrackingProtectionAction.EnterDetailsMode( @@ -44,23 +109,27 @@ class TrackingProtectionPanelInteractorTest { } @Test - fun selectTrackingProtectionSettings() { - var openSettings = false - val interactor = TrackingProtectionPanelInteractor( - mockk(), - { }, - { openSettings = true } - ) + fun `WHEN selectTrackingProtectionSettings is called THEN openTrackingProtectionSettings should be invoked`() { interactor.selectTrackingProtectionSettings() + assertEquals(true, openSettings) } @Test - fun onBackPressed() { - val store: TrackingProtectionStore = mockk(relaxed = true) - val interactor = - TrackingProtectionPanelInteractor(store, {}, {}) + fun `WHEN onBackPressed is called THEN call popBackStack and navigate`() { interactor.onBackPressed() + + verify { + navController.popBackStack() + + navController.navigate(any()) + } + } + + @Test + fun `WHEN onExitDetailMode is called THEN store should dispatch ExitDetailsMode action`() { + interactor.onExitDetailMode() + verify { store.dispatch(TrackingProtectionAction.ExitDetailsMode) } } } diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelViewTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelViewTest.kt index d89aa6e16..c9c43f914 100644 --- a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelViewTest.kt +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionPanelViewTest.kt @@ -87,8 +87,14 @@ class TrackingProtectionPanelViewTest { } @Test - fun testDetailsBack() { + fun testExistDetailModed() { view.details_back.performClick() + verify { interactor.onExitDetailMode() } + } + + @Test + fun testDetailsBack() { + view.navigate_back.performClick() verify { interactor.onBackPressed() } }