From f7653f3fd5a76f1d255ae2ea63383f2f2076abac Mon Sep 17 00:00:00 2001 From: Emily Kager Date: Tue, 3 Sep 2019 17:43:52 -0700 Subject: [PATCH] For #3700 - Add Setting to Delete Data on "Quit" menu action --- app/build.gradle | 1 + .../fenix/browser/BaseBrowserFragment.kt | 3 +- .../fenix/components/metrics/Metrics.kt | 2 +- .../toolbar/BrowserToolbarController.kt | 7 +- .../components/toolbar/DefaultToolbarMenu.kt | 13 ++ .../fenix/components/toolbar/ToolbarMenu.kt | 1 + .../DeleteBrowsingDataOnQuitFragment.kt | 105 ++++++++++++++ .../fenix/settings/SettingsFragment.kt | 12 +- .../fenix/settings/SharedPreferenceUpdater.kt | 2 +- .../org/mozilla/fenix/utils/DeleteAndQuit.kt | 55 ++++++++ .../java/org/mozilla/fenix/utils/Settings.kt | 38 +++++ .../delete_browsing_category_checkbox.xml | 50 +++++++ app/src/main/res/navigation/nav_graph.xml | 7 + app/src/main/res/values/preference_keys.xml | 9 ++ .../delete_browsing_data_quit_preferences.xml | 41 ++++++ app/src/main/res/xml/preferences.xml | 4 + .../DefaultBrowserToolbarControllerTest.kt | 55 +++++++- .../mozilla/fenix/utils/DeleteAndQuitTest.kt | 133 ++++++++++++++++++ .../org/mozilla/fenix/utils/SettingsTest.kt | 45 ++++++ buildSrc/src/main/java/Dependencies.kt | 3 +- 20 files changed, 574 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataOnQuitFragment.kt create mode 100644 app/src/main/java/org/mozilla/fenix/utils/DeleteAndQuit.kt create mode 100644 app/src/main/res/layout/delete_browsing_category_checkbox.xml create mode 100644 app/src/main/res/xml/delete_browsing_data_quit_preferences.xml create mode 100644 app/src/test/java/org/mozilla/fenix/utils/DeleteAndQuitTest.kt diff --git a/app/build.gradle b/app/build.gradle index 6b5de47695f2..f98c5ecf3ede 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -329,6 +329,7 @@ dependencies { implementation Deps.kotlin_stdlib implementation Deps.kotlin_coroutines + testImplementation Deps.kotlin_coroutines_test implementation Deps.androidx_appcompat implementation Deps.androidx_constraintlayout implementation Deps.androidx_coordinatorlayout 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 f6a656315ca7..2f92a2a1e2e0 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 it.action = Intent.ACTION_VIEW it.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 4aa09d800736..ac81fbf4b355 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 @@ -253,7 +253,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 18856c31786a..03a3907c5b66 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 @@ -49,7 +51,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 handleToolbarClick() { @@ -156,6 +159,7 @@ class DefaultBrowserToolbarController( // Close this activity since it is no longer displaying any session (context as Activity).finish() } + ToolbarMenu.Item.Quit -> context.deleteAndQuit(scope) } } @@ -184,6 +188,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 f43a4cd26060..fc658964a046 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_delete, + 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 6d443772f56f..29cdeba66d17 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/DeleteBrowsingDataOnQuitFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/DeleteBrowsingDataOnQuitFragment.kt new file mode 100644 index 000000000000..761becae89a3 --- /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() + + class CheckboxUpdater : 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 + } + } + + class SwitchUpdater : 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 cf29fd42432e..6fe2e3e1411f 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/SettingsFragment.kt @@ -39,6 +39,7 @@ import org.mozilla.fenix.R.string.pref_key_account_auth_error import org.mozilla.fenix.R.string.pref_key_account_category 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 @@ -141,7 +142,7 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver { updateAccountUIState(context!!, requireComponents.backgroundServices.accountManager.accountProfile()) } - @Suppress("ComplexMethod") + @Suppress("ComplexMethod", "LongMethod") override fun onPreferenceTreeClick(preference: Preference): Boolean { when (preference.key) { resources.getString(pref_key_search_engine_settings) -> { @@ -195,6 +196,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() } @@ -327,6 +331,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 d33c62959eac..99947ca1dd5c 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.utils.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 000000000000..3f53d3668a3d --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/utils/DeleteAndQuit.kt @@ -0,0 +1,55 @@ +/* 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 kotlinx.coroutines.withContext +import mozilla.components.concept.engine.Engine +import org.mozilla.fenix.ext.asActivity +import org.mozilla.fenix.ext.components + +/** + * Deletes selected browsing data and finishes the activity + */ +fun Context.deleteAndQuit(coroutineScope: CoroutineScope) { + coroutineScope.launch { + runBlocking { + if (Settings.getInstance(this@deleteAndQuit).deleteCacheOnQuit) { + withContext(coroutineContext) { + this@deleteAndQuit.components.core.engine.clearData(Engine.BrowsingData.allCaches()) + } + } + if (Settings.getInstance(this@deleteAndQuit).deleteTabsOnQuit) { + withContext(coroutineContext) { + this@deleteAndQuit.components.useCases.tabsUseCases.removeAllTabs.invoke() + } + } + if (Settings.getInstance(this@deleteAndQuit).deletePermissionsOnQuit) { + launch(Dispatchers.IO) { + this@deleteAndQuit.components.core.permissionStorage.deleteAllSitePermissions() + } + } + if (Settings.getInstance(this@deleteAndQuit).deleteCookiesOnQuit) { + withContext(coroutineContext) { + this@deleteAndQuit.components.core.engine.clearData( + Engine.BrowsingData.select( + Engine.BrowsingData.COOKIES + ) + ) + } + } + if (Settings.getInstance(this@deleteAndQuit).deleteHistoryOnQuit) { + withContext(coroutineContext) { + this@deleteAndQuit.components.core.historyStorage.deleteEverything() + } + } + } + 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 057cba83b7b3..f391980ff467 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -142,6 +142,44 @@ class Settings private constructor( default = false ) + 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) 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 000000000000..5d2d676f0901 --- /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 fb518b4df0a8..b10b5baea57c 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -350,6 +350,9 @@ + + diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index da1a71a8de52..f03d7ba82808 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -15,6 +15,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 000000000000..3a15926e5bd7 --- /dev/null +++ b/app/src/main/res/xml/delete_browsing_data_quit_preferences.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 59cff6e3cbb4..e44a17bec862 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -66,6 +66,10 @@ 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, @@ -75,7 +89,8 @@ class DefaultBrowserToolbarControllerTest { viewModel = viewModel, getSupportUrl = getSupportUrl, openInFenixIntent = openInFenixIntent, - bottomSheetBehavior = bottomSheetBehavior + bottomSheetBehavior = bottomSheetBehavior, + scope = scope ) mockkStatic( @@ -83,12 +98,23 @@ 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 every { context.components.core.sessionManager.selectedSession } returns currentSession } + @After + fun tearDown() { + Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher + mainThreadSurrogate.close() + } + @Test fun handleToolbarClick() { every { currentSession.id } returns "1" @@ -178,7 +204,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 @@ -196,7 +223,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 @@ -279,7 +307,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" + ) + ) } } @@ -357,7 +390,8 @@ class DefaultBrowserToolbarControllerTest { viewModel = viewModel, getSupportUrl = getSupportUrl, openInFenixIntent = openInFenixIntent, - bottomSheetBehavior = bottomSheetBehavior + bottomSheetBehavior = bottomSheetBehavior, + scope = scope ) val sessionManager: SessionManager = mockk(relaxed = true) @@ -375,4 +409,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 000000000000..e1445a9454a9 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/utils/DeleteAndQuitTest.kt @@ -0,0 +1,133 @@ +/* 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() + + permissionStorage.deleteAllSitePermissions() + + engine.clearData( + Engine.BrowsingData.select( + Engine.BrowsingData.COOKIES + ) + ) + + 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 a7901283ebc7..8f828bf2ed4a 100644 --- a/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt +++ b/app/src/test/java/org/mozilla/fenix/utils/SettingsTest.kt @@ -53,6 +53,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/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index b6ab979ac2dc..f9f966c59fd0 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -4,7 +4,7 @@ object Versions { const val kotlin = "1.3.30" - const val coroutines = "1.3.0-RC2" + const val coroutines = "1.3.1" const val android_gradle_plugin = "3.5.0" const val newest_r8 = "ceaee94e172c6c057cc05e646f5324853fc5d4c5" const val rxAndroid = "2.1.0" @@ -73,6 +73,7 @@ object Deps { const val tools_kotlingradle = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" const val kotlin_stdlib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" const val kotlin_coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines}" + const val kotlin_coroutines_test = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Versions.coroutines}" const val kotlin_coroutines_android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}" const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"