diff --git a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt index d888b85ac..cc3d18d2d 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BrowserFragment.kt @@ -5,17 +5,12 @@ package org.mozilla.fenix.browser import android.content.Context -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.WindowManager import android.widget.Button -import android.widget.ImageView -import android.widget.PopupWindow import android.widget.RadioButton import androidx.appcompat.widget.AppCompatImageView import androidx.core.content.ContextCompat @@ -24,7 +19,6 @@ import androidx.transition.TransitionInflater import com.google.android.material.snackbar.Snackbar import kotlinx.android.synthetic.main.fragment_browser.view.* import kotlinx.android.synthetic.main.fragment_home.* -import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.* import kotlinx.coroutines.ExperimentalCoroutinesApi import mozilla.components.browser.session.Session import mozilla.components.feature.contextmenu.ContextMenuCandidate @@ -35,21 +29,19 @@ import mozilla.components.feature.tabs.WindowFeature import mozilla.components.lib.state.ext.consumeFrom import mozilla.components.support.base.feature.BackHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper -import org.jetbrains.anko.dimen import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getDimenInDip -import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.sessioncontrol.SessionControlChange import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.mvi.getManagedEmitter +import org.mozilla.fenix.trackingprotection.TrackingProtectionOverlay /** * Fragment used for browsing the web within the main app. @@ -127,17 +119,16 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler { override fun onStart() { super.onStart() subscribeToTabCollections() - getSessionById()?.register(toolbarSessionObserver, this, autoPause = true) - } - private val toolbarSessionObserver = object : Session.Observer { - override fun onLoadingStateChanged(session: Session, loading: Boolean) { - if (!loading && - shouldShowTrackingProtectionOnboarding(session) - ) { - showTrackingProtectionOnboarding() - } - } + val toolbarSessionObserver = TrackingProtectionOverlay( + context = requireContext(), + settings = requireContext().settings(), + toolbar = browserToolbarView.view, + trackingProtectionIcon = browserToolbarView + .view + .findViewById(R.id.mozac_browser_toolbar_tracking_protection_indicator) + ) + getSessionById()?.register(toolbarSessionObserver, this, autoPause = true) } override fun onResume() { @@ -254,54 +245,6 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler { } } - private fun showTrackingProtectionOnboarding() { - context?.let { - val layout = LayoutInflater.from(it) - .inflate(R.layout.tracking_protection_onboarding_popup, null) - layout.onboarding_message.text = - it.getString(R.string.etp_onboarding_message_2, getString(R.string.app_name)) - - val trackingOnboarding = PopupWindow( - layout, - it.dimen(R.dimen.tp_onboarding_width), - WindowManager.LayoutParams.WRAP_CONTENT - ).apply { - setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) - isOutsideTouchable = true - isFocusable = true - elevation = view!!.resources.getDimension(R.dimen.mozac_browser_menu_elevation) - animationStyle = R.style.Mozac_Browser_Menu_Animation_OverflowMenuBottom - } - - val closeButton = layout.findViewById(R.id.close_onboarding) - closeButton.increaseTapArea(BUTTON_INCREASE_DPS) - closeButton.setOnClickListener { - trackingOnboarding.dismiss() - } - - val tpIcon = - browserToolbarView - .view - .findViewById(R.id.mozac_browser_toolbar_tracking_protection_indicator) - - // Measure layout view - val spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) - layout.measure(spec, spec) - - val containerHeight = layout.measuredHeight - val triangleHeight = it.getDimenInDip(R.dimen.tp_onboarding_triangle_height).toInt() - - val xOffset = it.dimen(R.dimen.tp_onboarding_x_offset) - - // Positioning the popup above the tp anchor. - val yOffset = - -containerHeight - (browserToolbarView.view.height / THREE * 2) + triangleHeight - - trackingOnboarding.showAsDropDown(tpIcon, xOffset, yOffset) - it.settings().incrementTrackingProtectionOnboardingCount() - } - } - override fun getContextMenuCandidates( context: Context, view: View @@ -316,10 +259,6 @@ class BrowserFragment : BaseBrowserFragment(), BackHandler { ) ) - private fun shouldShowTrackingProtectionOnboarding(session: Session) = - context?.settings()?.shouldShowTrackingProtectionOnboarding ?: false && - session.trackerBlockingEnabled && session.trackersBlocked.isNotEmpty() - companion object { private const val THREE = 3 private const val BUTTON_INCREASE_DPS = 12 diff --git a/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt new file mode 100644 index 000000000..443687cb9 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlay.kt @@ -0,0 +1,89 @@ +/* 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.content.Context +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.LayoutInflater +import android.view.View +import android.view.View.MeasureSpec +import android.view.WindowManager +import android.widget.ImageView +import android.widget.PopupWindow +import kotlinx.android.synthetic.main.tracking_protection_onboarding_popup.view.* +import mozilla.components.browser.session.Session +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.getDimenInDip +import org.mozilla.fenix.ext.increaseTapArea +import org.mozilla.fenix.utils.Settings + +/** + * Displays an overlay above the tracking protection button in the browser toolbar + * to onboard the user about tracking protection. + */ +class TrackingProtectionOverlay( + private val context: Context, + private val settings: Settings, + private val toolbar: View, + private val trackingProtectionIcon: View +) : Session.Observer { + + override fun onLoadingStateChanged(session: Session, loading: Boolean) { + if (!loading && shouldShowTrackingProtectionOnboarding(session)) { + showTrackingProtectionOnboarding() + } + } + + private fun shouldShowTrackingProtectionOnboarding(session: Session) = + settings.shouldShowTrackingProtectionOnboarding && + session.trackerBlockingEnabled && + session.trackersBlocked.isNotEmpty() + + @Suppress("MagicNumber", "InflateParams") + private fun showTrackingProtectionOnboarding() { + val layout = LayoutInflater.from(context) + .inflate(R.layout.tracking_protection_onboarding_popup, null) + layout.onboarding_message.text = + context.getString(R.string.etp_onboarding_message_2, context.getString(R.string.app_name)) + + val trackingOnboarding = PopupWindow( + layout, + context.resources.getDimensionPixelSize(R.dimen.tp_onboarding_width), + WindowManager.LayoutParams.WRAP_CONTENT + ).apply { + setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) + isOutsideTouchable = true + isFocusable = true + elevation = context.resources.getDimension(R.dimen.mozac_browser_menu_elevation) + animationStyle = R.style.Mozac_Browser_Menu_Animation_OverflowMenuBottom + } + + val closeButton = layout.findViewById(R.id.close_onboarding) + closeButton.increaseTapArea(BUTTON_INCREASE_DPS) + closeButton.setOnClickListener { + trackingOnboarding.dismiss() + } + + // Measure layout view + val spec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + layout.measure(spec, spec) + + val containerHeight = layout.measuredHeight + val triangleHeight = context.getDimenInDip(R.dimen.tp_onboarding_triangle_height).toInt() + + val xOffset = context.resources.getDimensionPixelSize(R.dimen.tp_onboarding_x_offset) + + // Positioning the popup above the tp anchor. + val yOffset = -containerHeight - (toolbar.height / 3 * 2) + triangleHeight + + trackingOnboarding.showAsDropDown(trackingProtectionIcon, xOffset, yOffset) + settings.incrementTrackingProtectionOnboardingCount() + } + + private companion object { + private const val BUTTON_INCREASE_DPS = 12 + } +} diff --git a/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt new file mode 100644 index 000000000..446a6d642 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/trackingprotection/TrackingProtectionOverlayTest.kt @@ -0,0 +1,91 @@ +/* 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.content.Context +import android.view.View +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import mozilla.components.browser.session.Session +import mozilla.components.support.test.robolectric.testContext +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.TestApplication +import org.mozilla.fenix.utils.Settings +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(application = TestApplication::class) +class TrackingProtectionOverlayTest { + + private lateinit var context: Context + private lateinit var settings: Settings + private lateinit var toolbar: View + private lateinit var icon: View + private lateinit var session: Session + private lateinit var overlay: TrackingProtectionOverlay + + @Before + fun setup() { + context = spyk(testContext) + settings = mockk(relaxed = true) + toolbar = mockk(relaxed = true) + icon = mockk(relaxed = true) + session = mockk(relaxed = true) + + overlay = TrackingProtectionOverlay(context, settings, toolbar, icon) + } + + @Test + fun `no-op when loading`() { + every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { session.trackerBlockingEnabled } returns true + every { session.trackersBlocked } returns listOf(mockk()) + + overlay.onLoadingStateChanged(session, loading = true) + verify(exactly = 0) { settings.incrementTrackingProtectionOnboardingCount() } + } + + @Test + fun `no-op when should not show onboarding`() { + every { settings.shouldShowTrackingProtectionOnboarding } returns false + + overlay.onLoadingStateChanged(session, loading = false) + verify(exactly = 0) { settings.incrementTrackingProtectionOnboardingCount() } + } + + @Test + fun `no-op when tracking protection disabled`() { + every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { session.trackerBlockingEnabled } returns false + + overlay.onLoadingStateChanged(session, loading = false) + verify(exactly = 0) { settings.incrementTrackingProtectionOnboardingCount() } + } + + @Test + fun `no-op when no trackers blocked`() { + every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { session.trackerBlockingEnabled } returns true + every { session.trackersBlocked } returns emptyList() + + overlay.onLoadingStateChanged(session, loading = false) + verify(exactly = 0) { settings.incrementTrackingProtectionOnboardingCount() } + } + + @Test + fun `show onboarding when trackers are blocked`() { + every { settings.shouldShowTrackingProtectionOnboarding } returns true + every { session.trackerBlockingEnabled } returns true + every { session.trackersBlocked } returns listOf(mockk()) + + overlay.onLoadingStateChanged(session, loading = false) + verify { settings.incrementTrackingProtectionOnboardingCount() } + } +}