diff --git a/app/metrics.yaml b/app/metrics.yaml index 04fced3ef..512fc410c 100644 --- a/app/metrics.yaml +++ b/app/metrics.yaml @@ -78,11 +78,12 @@ events: description: > A string containing the name of the item the user tapped. These items include: Settings, Library, Help, Desktop Site toggle on/off, Find in Page, New Tab, - Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button + Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button, Quit bugs: - 1024 data_reviews: - https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708 + - https://github.com/mozilla-mobile/fenix/pull/5098#issuecomment-529658996 notification_emails: - fenix-core@mozilla.com expires: "2020-03-01" diff --git a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt index efb942db1..443263e49 100644 --- a/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt +++ b/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -47,4 +47,9 @@ object FeatureFlags { * setting screen for cookies, cached images and files, and site permissions. */ val granularDataDeletion = nightly or debug + + /** + * Gives option in Settings to Delete Browsing Data on new menu option Quit + */ + val deleteDataOnQuit = nightly or debug } diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 064a67a96..944a53b09 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -171,7 +171,8 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs action = Intent.ACTION_VIEW flags = Intent.FLAG_ACTIVITY_NEW_TASK }, - bottomSheetBehavior = QuickActionSheetBehavior.from(nestedScrollQuickAction) + bottomSheetBehavior = QuickActionSheetBehavior.from(nestedScrollQuickAction), + scope = lifecycleScope ) browserInteractor = diff --git a/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt b/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt index 853b21dc3..a7e2566e3 100644 --- a/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt +++ b/app/src/main/java/org/mozilla/fenix/components/metrics/Metrics.kt @@ -266,7 +266,7 @@ sealed class Event { enum class Item { SETTINGS, LIBRARY, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB, NEW_PRIVATE_TAB, SHARE, REPORT_SITE_ISSUE, BACK, FORWARD, RELOAD, STOP, OPEN_IN_FENIX, - SAVE_TO_COLLECTION, ADD_TO_HOMESCREEN + SAVE_TO_COLLECTION, ADD_TO_HOMESCREEN, QUIT } override val extras: Map? diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt index 474b0c7c2..704faa147 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/BrowserToolbarController.kt @@ -8,6 +8,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import androidx.core.widget.NestedScrollView +import androidx.lifecycle.LifecycleCoroutineScope import androidx.navigation.NavController import com.google.android.material.bottomsheet.BottomSheetBehavior import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -29,6 +30,7 @@ import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.toTab import org.mozilla.fenix.lib.Do import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior +import org.mozilla.fenix.utils.deleteAndQuit /** * An interface that handles the view manipulation of the BrowserToolbar, triggered by the Interactor @@ -50,7 +52,8 @@ class DefaultBrowserToolbarController( private val viewModel: CreateCollectionViewModel, private val getSupportUrl: () -> String, private val openInFenixIntent: Intent, - private val bottomSheetBehavior: QuickActionSheetBehavior + private val bottomSheetBehavior: QuickActionSheetBehavior, + private val scope: LifecycleCoroutineScope ) : BrowserToolbarController { override fun handleToolbarPaste(text: String) { @@ -176,6 +179,7 @@ class DefaultBrowserToolbarController( // Close this activity since it is no longer displaying any session (context as Activity).finish() } + ToolbarMenu.Item.Quit -> context.deleteAndQuit(scope) } } @@ -204,6 +208,7 @@ class DefaultBrowserToolbarController( ToolbarMenu.Item.Share -> Event.BrowserMenuItemTapped.Item.SHARE ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN + ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT } context.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem)) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt index f43a4cd26..6c7a8d618 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/DefaultToolbarMenu.kt @@ -17,6 +17,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.ext.components import org.mozilla.fenix.theme.ThemeManager +import org.mozilla.fenix.utils.Settings class DefaultToolbarMenu( private val context: Context, @@ -203,6 +204,18 @@ class DefaultToolbarMenu( ) } + if (Settings.getInstance(context).shouldDeleteBrowsingDataOnQuit) { + items.add( + BrowserMenuImageText( + context.getString(R.string.delete_browsing_data_on_quit_action), + R.drawable.ic_exit, + ThemeManager.resolveAttribute(R.attr.primaryText, context) + ) { + onItemTapped.invoke(ToolbarMenu.Item.Quit) + } + ) + } + items.add( BrowserMenuDivider() ) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt index 6d443772f..29cdeba66 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/ToolbarMenu.kt @@ -25,6 +25,7 @@ interface ToolbarMenu { object OpenInFenix : Item() object SaveToCollection : Item() object AddToHomeScreen : Item() + object Quit : Item() } val menuBuilder: BrowserMenuBuilder diff --git a/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataController.kt b/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataController.kt index 14fae3f13..311b078e7 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataController.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataController.kt @@ -34,12 +34,19 @@ class DefaultDeleteBrowsingDataController( } override suspend fun deleteBrowsingData() { - withContext(coroutineContext) { - if (FeatureFlags.granularDataDeletion) { - context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) - } else { + if (FeatureFlags.granularDataDeletion) { + deleteHistoryAndDOMStorages() + } else { + withContext(coroutineContext) { context.components.core.engine.clearData(Engine.BrowsingData.all()) } + context.components.core.historyStorage.deleteEverything() + } + } + + suspend fun deleteHistoryAndDOMStorages() { + withContext(coroutineContext) { + context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) } context.components.core.historyStorage.deleteEverything() } diff --git a/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataOnQuitFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataOnQuitFragment.kt new file mode 100644 index 000000000..d8de98bc6 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataOnQuitFragment.kt @@ -0,0 +1,105 @@ +/* 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 + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.preference.CheckBoxPreference +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference +import org.mozilla.fenix.R +import org.mozilla.fenix.ext.getPreferenceKey +import org.mozilla.fenix.utils.Settings + +class DeleteBrowsingDataOnQuitFragment : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.delete_browsing_data_quit_preferences, rootKey) + } + + @Suppress("ComplexMethod") + override fun onResume() { + super.onResume() + activity?.title = getString(R.string.preferences_delete_browsing_data_on_quit) + (activity as AppCompatActivity).supportActionBar?.show() + + val checkboxUpdater = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + super.onPreferenceChange(preference, newValue) + if (!Settings.getInstance(preference.context).shouldDeleteAnyDataOnQuit()) { + findPreference( + getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit) + )?.apply { + isChecked = false + } + Settings.getInstance(preference.context).preferences.edit().putBoolean( + getString(R.string.pref_key_delete_browsing_data_on_quit), + false + ).apply() + } + return true + } + } + + val switchUpdater = object : SharedPreferenceUpdater() { + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + setAllCheckboxes(newValue as Boolean) + return super.onPreferenceChange(preference, newValue) + } + } + + findPreference(getPreferenceKey(R.string.pref_key_delete_open_tabs_on_quit))?.apply { + onPreferenceChangeListener = checkboxUpdater + } + findPreference(getPreferenceKey(R.string.pref_key_delete_browsing_history_on_quit))?.apply { + onPreferenceChangeListener = checkboxUpdater + } + findPreference(getPreferenceKey(R.string.pref_key_delete_caches_on_quit))?.apply { + onPreferenceChangeListener = checkboxUpdater + } + findPreference(getPreferenceKey(R.string.pref_key_delete_permissions_on_quit))?.apply { + onPreferenceChangeListener = checkboxUpdater + } + findPreference(getPreferenceKey(R.string.pref_key_delete_cookies_on_quit))?.apply { + onPreferenceChangeListener = checkboxUpdater + } + + // Delete Browsing Data on Quit Switch + val deleteOnQuitKey = getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit) + findPreference(deleteOnQuitKey)?.apply { + onPreferenceChangeListener = switchUpdater + isChecked = Settings.getInstance(context!!).shouldDeleteBrowsingDataOnQuit + } + } + + private fun setAllCheckboxes(newValue: Boolean) { + val openTabs = + findPreference(getPreferenceKey(R.string.pref_key_delete_open_tabs_on_quit)) + val history = + findPreference(getPreferenceKey(R.string.pref_key_delete_browsing_history_on_quit)) + val cache = + findPreference(getPreferenceKey(R.string.pref_key_delete_caches_on_quit)) + val permissions = + findPreference(getPreferenceKey(R.string.pref_key_delete_permissions_on_quit)) + val cookies = + findPreference(getPreferenceKey(R.string.pref_key_delete_cookies_on_quit)) + + openTabs?.isChecked = newValue + history?.isChecked = newValue + cache?.isChecked = newValue + permissions?.isChecked = newValue + cookies?.isChecked = newValue + + Settings.getInstance(context!!).preferences.edit().putBoolean(openTabs?.key, newValue) + .apply() + Settings.getInstance(context!!).preferences.edit().putBoolean(history?.key, newValue) + .apply() + Settings.getInstance(context!!).preferences.edit().putBoolean(cache?.key, newValue).apply() + Settings.getInstance(context!!).preferences.edit().putBoolean(permissions?.key, newValue) + .apply() + Settings.getInstance(context!!).preferences.edit().putBoolean(cookies?.key, newValue) + .apply() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt index e2c7a0e58..c7ff37ce4 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -29,6 +29,7 @@ import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.Profile import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.Config +import org.mozilla.fenix.FeatureFlags import org.mozilla.fenix.FenixApplication import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R @@ -40,6 +41,7 @@ import org.mozilla.fenix.R.string.pref_key_account_category import org.mozilla.fenix.R.string.pref_key_add_private_browsing_shortcut import org.mozilla.fenix.R.string.pref_key_data_choices import org.mozilla.fenix.R.string.pref_key_delete_browsing_data +import org.mozilla.fenix.R.string.pref_key_delete_browsing_data_on_quit_preference import org.mozilla.fenix.R.string.pref_key_help import org.mozilla.fenix.R.string.pref_key_language import org.mozilla.fenix.R.string.pref_key_leakcanary @@ -99,6 +101,14 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver { isVisible = false } } + + if (FeatureFlags.deleteDataOnQuit) { + findPreference( + getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit_preference) + )?.apply { + isVisible = true + } + } } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { @@ -203,6 +213,9 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver { resources.getString(pref_key_delete_browsing_data) -> { navigateToDeleteBrowsingData() } + resources.getString(pref_key_delete_browsing_data_on_quit_preference) -> { + navigateToDeleteBrowsingDataOnQuit() + } resources.getString(pref_key_theme) -> { navigateToThemeSettings() } @@ -335,6 +348,12 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver { Navigation.findNavController(view!!).navigate(directions) } + private fun navigateToDeleteBrowsingDataOnQuit() { + val directions = + SettingsFragmentDirections.actionSettingsFragmentToDeleteBrowsingDataOnQuitFragment() + Navigation.findNavController(view!!).navigate(directions) + } + override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { lifecycleScope.launch { context?.let { diff --git a/app/src/main/java/org/mozilla/fenix/settings/SharedPreferenceUpdater.kt b/app/src/main/java/org/mozilla/fenix/settings/SharedPreferenceUpdater.kt index f8f81b4c6..ddccbe3de 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SharedPreferenceUpdater.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SharedPreferenceUpdater.kt @@ -8,7 +8,7 @@ import org.mozilla.fenix.ext.settings * Updates the corresponding [android.content.SharedPreferences] when the boolean [Preference] is changed. * The preference key is used as the shared preference key. */ -class SharedPreferenceUpdater : Preference.OnPreferenceChangeListener { +open class SharedPreferenceUpdater : Preference.OnPreferenceChangeListener { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { val newBooleanValue = newValue as? Boolean ?: return false diff --git a/app/src/main/java/org/mozilla/fenix/utils/DeleteAndQuit.kt b/app/src/main/java/org/mozilla/fenix/utils/DeleteAndQuit.kt new file mode 100644 index 000000000..cf02b7fe6 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/utils/DeleteAndQuit.kt @@ -0,0 +1,43 @@ +/* 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.utils + +import android.content.Context +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.mozilla.fenix.ext.asActivity +import org.mozilla.fenix.settings.DefaultDeleteBrowsingDataController + +/** + * Deletes selected browsing data and finishes the activity + */ +fun Context.deleteAndQuit(coroutineScope: CoroutineScope) { + coroutineScope.launch { + runBlocking { + val controller = + DefaultDeleteBrowsingDataController(this@deleteAndQuit, coroutineContext) + if (Settings.getInstance(this@deleteAndQuit).deleteCacheOnQuit) { + controller.deleteCachedFiles() + } + if (Settings.getInstance(this@deleteAndQuit).deleteTabsOnQuit) { + controller.deleteTabs() + } + if (Settings.getInstance(this@deleteAndQuit).deletePermissionsOnQuit) { + launch(Dispatchers.IO) { + controller.deleteSitePermissions() + } + } + if (Settings.getInstance(this@deleteAndQuit).deleteCookiesOnQuit) { + controller.deleteCookies() + } + if (Settings.getInstance(this@deleteAndQuit).deleteHistoryOnQuit) { + controller.deleteHistoryAndDOMStorages() + } + } + this@deleteAndQuit.asActivity()?.finish() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index 2ab0d117e..5446c3859 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -27,6 +27,7 @@ import java.security.InvalidParameterException /** * A simple wrapper for SharedPreferences that makes reading preference a little bit easier. */ +@Suppress("LargeClass") class Settings private constructor( context: Context, private val isCrashReportEnabledInBuild: Boolean @@ -168,6 +169,44 @@ class Settings private constructor( true ) + var shouldDeleteBrowsingDataOnQuit by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit), + default = false + ) + + var deleteTabsOnQuit by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_delete_open_tabs_on_quit), + default = false + ) + + var deleteHistoryOnQuit by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_delete_browsing_history_on_quit), + default = false + ) + + var deleteCookiesOnQuit by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_delete_cookies_on_quit), + default = false + ) + + var deleteCacheOnQuit by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_delete_caches_on_quit), + default = false + ) + + var deletePermissionsOnQuit by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_delete_permissions_on_quit), + default = false + ) + + fun shouldDeleteAnyDataOnQuit(): Boolean { + return deleteCacheOnQuit || + deleteCookiesOnQuit || + deleteHistoryOnQuit || + deletePermissionsOnQuit || + deleteTabsOnQuit + } + val themeSettingString: String get() = when { shouldFollowDeviceTheme -> appContext.getString(R.string.preference_follow_device_theme) @@ -265,7 +304,10 @@ class Settings private constructor( ) private val numTimesPrivateModeOpened: Int - get() = preferences.getInt(appContext.getPreferenceKey(R.string.pref_key_private_mode_opened), 0) + get() = preferences.getInt( + appContext.getPreferenceKey(R.string.pref_key_private_mode_opened), + 0 + ) val showPrivateModeContextualFeatureRecommender: Boolean get() { @@ -275,7 +317,7 @@ class Settings private constructor( val showCondition = (numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_INSTALLED && focusInstalled) || - (numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled) + (numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED && !focusInstalled) if (showCondition && !showedPrivateModeContextualFeatureRecommender) { showedPrivateModeContextualFeatureRecommender = true diff --git a/app/src/main/res/drawable/ic_exit.xml b/app/src/main/res/drawable/ic_exit.xml new file mode 100644 index 000000000..a0d5829fa --- /dev/null +++ b/app/src/main/res/drawable/ic_exit.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/layout/delete_browsing_category_checkbox.xml b/app/src/main/res/layout/delete_browsing_category_checkbox.xml new file mode 100644 index 000000000..81d45856c --- /dev/null +++ b/app/src/main/res/layout/delete_browsing_category_checkbox.xml @@ -0,0 +1,50 @@ + + + + + + + + + + diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 35eec3d24..3b9b05dbb 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -110,7 +110,7 @@ android:name="pastedText" android:defaultValue="@null" app:argType="string" - app:nullable="true"/> + app:nullable="true" /> + app:destination="@id/browserFragment" /> @@ -359,6 +359,9 @@ + + android:label="@string/preferences_sync" /> + diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 26244206c..88731cd26 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -16,6 +16,15 @@ pref_key_data_choices pref_key_privacy_link pref_key_delete_browsing_data + pref_key_delete_browsing_data_on_quit_preference + pref_key_delete_browsing_data_on_quit + pref_key_delete_open_tabs_on_quit + pref_key_delete_browsing_history_on_quit + pref_key_delete_cookies_on_quit + pref_key_delete_caches_on_quit + pref_key_delete_permissions_on_quit + pref_key_delete_browsing_data_on_quit_categories + pref_key_help pref_key_rate pref_key_feedback diff --git a/app/src/main/res/xml/delete_browsing_data_quit_preferences.xml b/app/src/main/res/xml/delete_browsing_data_quit_preferences.xml new file mode 100644 index 000000000..1b6605f66 --- /dev/null +++ b/app/src/main/res/xml/delete_browsing_data_quit_preferences.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 6d165fdb0..9634ccdb7 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -70,6 +70,11 @@ android:icon="@drawable/ic_delete" android:key="@string/pref_key_delete_browsing_data" android:title="@string/preferences_delete_browsing_data" /> + String = { "https://supportUrl.org" } private val openInFenixIntent: Intent = mockk(relaxed = true) private val currentSessionAsTab: Tab = mockk(relaxed = true) - private val bottomSheetBehavior: QuickActionSheetBehavior = mockk(relaxed = true) + private val bottomSheetBehavior: QuickActionSheetBehavior = + mockk(relaxed = true) private val metrics: MetricController = mockk(relaxed = true) private val sessionUseCases: SessionUseCases = mockk(relaxed = true) + private val scope: LifecycleCoroutineScope = mockk(relaxed = true) private lateinit var controller: DefaultBrowserToolbarController @Before fun setUp() { + Dispatchers.setMain(mainThreadSurrogate) + controller = DefaultBrowserToolbarController( context = context, navController = navController, @@ -74,7 +88,8 @@ class DefaultBrowserToolbarControllerTest { viewModel = viewModel, getSupportUrl = getSupportUrl, openInFenixIntent = openInFenixIntent, - bottomSheetBehavior = bottomSheetBehavior + bottomSheetBehavior = bottomSheetBehavior, + scope = scope ) mockkStatic( @@ -82,6 +97,11 @@ class DefaultBrowserToolbarControllerTest { ) every { any().toTab(any()) } returns currentSessionAsTab + mockkStatic( + "org.mozilla.fenix.utils.DeleteAndQuitKt" + ) + every { any().deleteAndQuit(any()) } just Runs + every { context.components.analytics } returns analytics every { analytics.metrics } returns metrics every { context.components.useCases.sessionUseCases } returns sessionUseCases @@ -117,6 +137,12 @@ class DefaultBrowserToolbarControllerTest { } } + @After + fun tearDown() { + Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher + mainThreadSurrogate.close() + } + @Test fun handleToolbarClick() { every { currentSession.id } returns "1" @@ -208,7 +234,8 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarRequestDesktopOnPress() { - val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = mockk(relaxed = true) + val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = + mockk(relaxed = true) val item = ToolbarMenu.Item.RequestDesktop(true) every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase @@ -226,7 +253,8 @@ class DefaultBrowserToolbarControllerTest { @Test fun handleToolbarRequestDesktopOffPress() { - val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = mockk(relaxed = true) + val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = + mockk(relaxed = true) val item = ToolbarMenu.Item.RequestDesktop(false) every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase @@ -309,7 +337,12 @@ class DefaultBrowserToolbarControllerTest { verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE)) } verify { // Hardcoded URL because this function modifies the URL with an apply - addTabUseCase.invoke(String.format(BrowserFragment.REPORT_SITE_ISSUE_URL, "https://mozilla.org")) + addTabUseCase.invoke( + String.format( + BrowserFragment.REPORT_SITE_ISSUE_URL, + "https://mozilla.org" + ) + ) } } @@ -386,7 +419,8 @@ class DefaultBrowserToolbarControllerTest { viewModel = viewModel, getSupportUrl = getSupportUrl, openInFenixIntent = openInFenixIntent, - bottomSheetBehavior = bottomSheetBehavior + bottomSheetBehavior = bottomSheetBehavior, + scope = scope ) val sessionManager: SessionManager = mockk(relaxed = true) @@ -404,4 +438,13 @@ class DefaultBrowserToolbarControllerTest { verify { context.startActivity(openInFenixIntent) } verify { context.finish() } } + + @Test + fun handleToolbarQuitPress() { + val item = ToolbarMenu.Item.Quit + + controller.handleToolbarItemInteraction(item) + + verify { context.deleteAndQuit(scope) } + } } diff --git a/app/src/test/java/org/mozilla/fenix/utils/DeleteAndQuitTest.kt b/app/src/test/java/org/mozilla/fenix/utils/DeleteAndQuitTest.kt new file mode 100644 index 000000000..77f979432 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/utils/DeleteAndQuitTest.kt @@ -0,0 +1,140 @@ +/* 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/. */ + +@file:Suppress("DEPRECATION") + +package org.mozilla.fenix.utils + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.ObsoleteCoroutinesApi +import kotlinx.coroutines.newSingleThreadContext +import kotlinx.coroutines.test.resetMain +import kotlinx.coroutines.test.runBlockingTest +import kotlinx.coroutines.test.setMain +import mozilla.components.browser.storage.sync.PlacesHistoryStorage +import mozilla.components.concept.engine.Engine +import mozilla.components.feature.tabs.TabsUseCases +import mozilla.components.support.test.robolectric.testContext +import org.junit.After +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.TestApplication +import org.mozilla.fenix.components.PermissionStorage +import org.mozilla.fenix.ext.asActivity +import org.mozilla.fenix.ext.clearAndCommit +import org.mozilla.fenix.ext.components +import org.robolectric.annotation.Config + +@ObsoleteCoroutinesApi +@ExperimentalCoroutinesApi +@RunWith(AndroidJUnit4::class) +@Config(application = TestApplication::class) +class DeleteAndQuitTest { + + private val mainThreadSurrogate = newSingleThreadContext("UI thread") + + private var context: HomeActivity = mockk(relaxed = true) + lateinit var settings: Settings + private val tabUseCases: TabsUseCases = mockk(relaxed = true) + private val historyStorage: PlacesHistoryStorage = mockk(relaxed = true) + private val permissionStorage: PermissionStorage = mockk(relaxed = true) + private val engine: Engine = mockk(relaxed = true) + private val removeAllTabsUseCases: TabsUseCases.RemoveAllTabsUseCase = mockk(relaxed = true) + + @Before + fun setUp() { + settings = Settings.getInstance(testContext).apply { + clear() + } + + Dispatchers.setMain(mainThreadSurrogate) + + every { context.components.core.historyStorage } returns historyStorage + every { context.components.core.permissionStorage } returns permissionStorage + every { context.components.useCases.tabsUseCases } returns tabUseCases + every { tabUseCases.removeAllTabs } returns removeAllTabsUseCases + every { context.components.core.engine } returns engine + } + + @After + fun tearDown() { + Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher + mainThreadSurrogate.close() + } + + private fun Settings.clear() { + preferences.clearAndCommit() + } + + @Test + fun `delete only tabs and quit`() = runBlockingTest { + // When + settings.deleteTabsOnQuit = true + + context.deleteAndQuit(this) + + verify { + removeAllTabsUseCases.invoke() + context.asActivity()?.finish() + } + + verify(exactly = 0) { + historyStorage + + engine.clearData( + Engine.BrowsingData.select( + Engine.BrowsingData.COOKIES + ) + ) + + permissionStorage.deleteAllSitePermissions() + + engine.clearData(Engine.BrowsingData.allCaches()) + } + } + + @Test + fun `delete everything and quit`() = runBlockingTest { + // When + settings.deleteTabsOnQuit = true + settings.deletePermissionsOnQuit = true + settings.deleteHistoryOnQuit = true + settings.deleteCookiesOnQuit = true + settings.deleteCacheOnQuit = true + + context.deleteAndQuit(this) + + verify(exactly = 1) { + engine.clearData(Engine.BrowsingData.allCaches()) + + removeAllTabsUseCases.invoke() + + engine.clearData( + Engine.BrowsingData.select(Engine.BrowsingData.ALL_SITE_SETTINGS) + ) + + permissionStorage.deleteAllSitePermissions() + + engine.clearData( + Engine.BrowsingData.select( + Engine.BrowsingData.COOKIES, + Engine.BrowsingData.AUTH_SESSIONS + ) + ) + + engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) + + historyStorage + + context.asActivity()?.finish() + } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt index cdaeeead7..a80030f5a 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -54,6 +54,51 @@ class SettingsTest { assertFalse(settings.usePrivateMode) } + @Test + fun clearDataOnQuit() { + // When just created + // Then + assertFalse(settings.shouldDeleteBrowsingDataOnQuit) + + // When + settings.shouldDeleteBrowsingDataOnQuit = true + + // Then + assertTrue(settings.shouldDeleteBrowsingDataOnQuit) + + // When + settings.shouldDeleteBrowsingDataOnQuit = false + + // Then + assertFalse(settings.shouldDeleteBrowsingDataOnQuit) + } + + @Test + fun clearAnyDataOnQuit() { + // When just created + // Then + assertFalse(settings.shouldDeleteAnyDataOnQuit()) + + // When + settings.deleteTabsOnQuit = true + + // Then + assertTrue(settings.shouldDeleteAnyDataOnQuit()) + + // When + settings.deletePermissionsOnQuit = true + + // Then + assertTrue(settings.shouldDeleteAnyDataOnQuit()) + + // When + settings.deletePermissionsOnQuit = false + settings.deleteTabsOnQuit = false + + // Then + assertFalse(settings.shouldDeleteAnyDataOnQuit()) + } + @Test fun defaultSearchEngineName() { // When just created diff --git a/docs/metrics.md b/docs/metrics.md index ef4c2e679..70744f4d7 100644 --- a/docs/metrics.md +++ b/docs/metrics.md @@ -76,11 +76,12 @@ event A browser menu item was tapped link + link +Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button, Quit
itemA string containing the name of the item the user tapped. These items include: Settings, Library, Help, Desktop Site toggle on/off, Find in Page, New Tab, -Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button
2020-03-01