1
0
Fork 0

For #3700 - Add Setting to Delete Data on "Quit" menu action

master
Emily Kager 2019-09-10 13:29:21 -07:00 committed by Emily Kager
parent ddc1b2e648
commit e3209dcc84
23 changed files with 616 additions and 21 deletions

View File

@ -78,11 +78,12 @@ events:
description: > description: >
A string containing the name of the item the user tapped. These items include: 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, 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: bugs:
- 1024 - 1024
data_reviews: data_reviews:
- https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708 - https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708
- https://github.com/mozilla-mobile/fenix/pull/5098#issuecomment-529658996
notification_emails: notification_emails:
- fenix-core@mozilla.com - fenix-core@mozilla.com
expires: "2020-03-01" expires: "2020-03-01"

View File

@ -47,4 +47,9 @@ object FeatureFlags {
* setting screen for cookies, cached images and files, and site permissions. * setting screen for cookies, cached images and files, and site permissions.
*/ */
val granularDataDeletion = nightly or debug val granularDataDeletion = nightly or debug
/**
* Gives option in Settings to Delete Browsing Data on new menu option Quit
*/
val deleteDataOnQuit = nightly or debug
} }

View File

@ -171,7 +171,8 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler, SessionManager.Obs
action = Intent.ACTION_VIEW action = Intent.ACTION_VIEW
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
}, },
bottomSheetBehavior = QuickActionSheetBehavior.from(nestedScrollQuickAction) bottomSheetBehavior = QuickActionSheetBehavior.from(nestedScrollQuickAction),
scope = lifecycleScope
) )
browserInteractor = browserInteractor =

View File

@ -266,7 +266,7 @@ sealed class Event {
enum class Item { enum class Item {
SETTINGS, LIBRARY, HELP, DESKTOP_VIEW_ON, DESKTOP_VIEW_OFF, FIND_IN_PAGE, NEW_TAB, 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, 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<Events.browserMenuActionKeys, String>? override val extras: Map<Events.browserMenuActionKeys, String>?

View File

@ -8,6 +8,7 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.navigation.NavController import androidx.navigation.NavController
import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -29,6 +30,7 @@ import org.mozilla.fenix.ext.nav
import org.mozilla.fenix.ext.toTab import org.mozilla.fenix.ext.toTab
import org.mozilla.fenix.lib.Do import org.mozilla.fenix.lib.Do
import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior 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 * 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 viewModel: CreateCollectionViewModel,
private val getSupportUrl: () -> String, private val getSupportUrl: () -> String,
private val openInFenixIntent: Intent, private val openInFenixIntent: Intent,
private val bottomSheetBehavior: QuickActionSheetBehavior<NestedScrollView> private val bottomSheetBehavior: QuickActionSheetBehavior<NestedScrollView>,
private val scope: LifecycleCoroutineScope
) : BrowserToolbarController { ) : BrowserToolbarController {
override fun handleToolbarPaste(text: String) { override fun handleToolbarPaste(text: String) {
@ -176,6 +179,7 @@ class DefaultBrowserToolbarController(
// Close this activity since it is no longer displaying any session // Close this activity since it is no longer displaying any session
(context as Activity).finish() (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.Share -> Event.BrowserMenuItemTapped.Item.SHARE
ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION ToolbarMenu.Item.SaveToCollection -> Event.BrowserMenuItemTapped.Item.SAVE_TO_COLLECTION
ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN ToolbarMenu.Item.AddToHomeScreen -> Event.BrowserMenuItemTapped.Item.ADD_TO_HOMESCREEN
ToolbarMenu.Item.Quit -> Event.BrowserMenuItemTapped.Item.QUIT
} }
context.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem)) context.components.analytics.metrics.track(Event.BrowserMenuItemTapped(eventItem))

View File

@ -17,6 +17,7 @@ import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.ext.asActivity import org.mozilla.fenix.ext.asActivity
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.Settings
class DefaultToolbarMenu( class DefaultToolbarMenu(
private val context: Context, 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( items.add(
BrowserMenuDivider() BrowserMenuDivider()
) )

View File

@ -25,6 +25,7 @@ interface ToolbarMenu {
object OpenInFenix : Item() object OpenInFenix : Item()
object SaveToCollection : Item() object SaveToCollection : Item()
object AddToHomeScreen : Item() object AddToHomeScreen : Item()
object Quit : Item()
} }
val menuBuilder: BrowserMenuBuilder val menuBuilder: BrowserMenuBuilder

View File

@ -34,12 +34,19 @@ class DefaultDeleteBrowsingDataController(
} }
override suspend fun deleteBrowsingData() { override suspend fun deleteBrowsingData() {
withContext(coroutineContext) { if (FeatureFlags.granularDataDeletion) {
if (FeatureFlags.granularDataDeletion) { deleteHistoryAndDOMStorages()
context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.DOM_STORAGES)) } else {
} else { withContext(coroutineContext) {
context.components.core.engine.clearData(Engine.BrowsingData.all()) 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() context.components.core.historyStorage.deleteEverything()
} }

View File

@ -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<SwitchPreference>(
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<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_open_tabs_on_quit))?.apply {
onPreferenceChangeListener = checkboxUpdater
}
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_browsing_history_on_quit))?.apply {
onPreferenceChangeListener = checkboxUpdater
}
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_caches_on_quit))?.apply {
onPreferenceChangeListener = checkboxUpdater
}
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_permissions_on_quit))?.apply {
onPreferenceChangeListener = checkboxUpdater
}
findPreference<CheckBoxPreference>(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<SwitchPreference>(deleteOnQuitKey)?.apply {
onPreferenceChangeListener = switchUpdater
isChecked = Settings.getInstance(context!!).shouldDeleteBrowsingDataOnQuit
}
}
private fun setAllCheckboxes(newValue: Boolean) {
val openTabs =
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_open_tabs_on_quit))
val history =
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_browsing_history_on_quit))
val cache =
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_caches_on_quit))
val permissions =
findPreference<CheckBoxPreference>(getPreferenceKey(R.string.pref_key_delete_permissions_on_quit))
val cookies =
findPreference<CheckBoxPreference>(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()
}
}

View File

@ -29,6 +29,7 @@ import mozilla.components.concept.sync.OAuthAccount
import mozilla.components.concept.sync.Profile import mozilla.components.concept.sync.Profile
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.Config import org.mozilla.fenix.Config
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.FenixApplication import org.mozilla.fenix.FenixApplication
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.R 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_add_private_browsing_shortcut
import org.mozilla.fenix.R.string.pref_key_data_choices 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
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_help
import org.mozilla.fenix.R.string.pref_key_language import org.mozilla.fenix.R.string.pref_key_language
import org.mozilla.fenix.R.string.pref_key_leakcanary import org.mozilla.fenix.R.string.pref_key_leakcanary
@ -99,6 +101,14 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
isVisible = false isVisible = false
} }
} }
if (FeatureFlags.deleteDataOnQuit) {
findPreference<Preference>(
getPreferenceKey(R.string.pref_key_delete_browsing_data_on_quit_preference)
)?.apply {
isVisible = true
}
}
} }
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@ -203,6 +213,9 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
resources.getString(pref_key_delete_browsing_data) -> { resources.getString(pref_key_delete_browsing_data) -> {
navigateToDeleteBrowsingData() navigateToDeleteBrowsingData()
} }
resources.getString(pref_key_delete_browsing_data_on_quit_preference) -> {
navigateToDeleteBrowsingDataOnQuit()
}
resources.getString(pref_key_theme) -> { resources.getString(pref_key_theme) -> {
navigateToThemeSettings() navigateToThemeSettings()
} }
@ -335,6 +348,12 @@ class SettingsFragment : PreferenceFragmentCompat(), AccountObserver {
Navigation.findNavController(view!!).navigate(directions) Navigation.findNavController(view!!).navigate(directions)
} }
private fun navigateToDeleteBrowsingDataOnQuit() {
val directions =
SettingsFragmentDirections.actionSettingsFragmentToDeleteBrowsingDataOnQuitFragment()
Navigation.findNavController(view!!).navigate(directions)
}
override fun onAuthenticated(account: OAuthAccount, authType: AuthType) { override fun onAuthenticated(account: OAuthAccount, authType: AuthType) {
lifecycleScope.launch { lifecycleScope.launch {
context?.let { context?.let {

View File

@ -8,7 +8,7 @@ import org.mozilla.fenix.ext.settings
* Updates the corresponding [android.content.SharedPreferences] when the boolean [Preference] is changed. * Updates the corresponding [android.content.SharedPreferences] when the boolean [Preference] is changed.
* The preference key is used as the shared preference key. * 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 { override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean {
val newBooleanValue = newValue as? Boolean ?: return false val newBooleanValue = newValue as? Boolean ?: return false

View File

@ -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()
}
}

View File

@ -27,6 +27,7 @@ import java.security.InvalidParameterException
/** /**
* A simple wrapper for SharedPreferences that makes reading preference a little bit easier. * A simple wrapper for SharedPreferences that makes reading preference a little bit easier.
*/ */
@Suppress("LargeClass")
class Settings private constructor( class Settings private constructor(
context: Context, context: Context,
private val isCrashReportEnabledInBuild: Boolean private val isCrashReportEnabledInBuild: Boolean
@ -168,6 +169,44 @@ class Settings private constructor(
true 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 val themeSettingString: String
get() = when { get() = when {
shouldFollowDeviceTheme -> appContext.getString(R.string.preference_follow_device_theme) shouldFollowDeviceTheme -> appContext.getString(R.string.preference_follow_device_theme)
@ -265,7 +304,10 @@ class Settings private constructor(
) )
private val numTimesPrivateModeOpened: Int 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 val showPrivateModeContextualFeatureRecommender: Boolean
get() { get() {
@ -275,7 +317,7 @@ class Settings private constructor(
val showCondition = val showCondition =
(numTimesPrivateModeOpened == CFR_COUNT_CONDITION_FOCUS_INSTALLED && focusInstalled) || (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) { if (showCondition && !showedPrivateModeContextualFeatureRecommender) {
showedPrivateModeContextualFeatureRecommender = true showedPrivateModeContextualFeatureRecommender = true

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?primaryText"
android:pathData="M12,1a11,11 0,1 0,11 11A11,11 0,0 0,12 1zM12,21a9,9 0,1 1,9 -9,9 9,0 0,1 -9,9zM16.71,7.29a1,1 0,0 0,-1.42 0L12,10.59l-3.29,-3.3a1,1 0,1 0,-1.42 1.42l3.3,3.29 -3.3,3.29a1,1 0,0 0,0 1.42,1 1,0 0,0 1.42,0l3.29,-3.3 3.29,3.3a1,1 0,0 0,1.42 0,1 1,0 0,0 0,-1.42L13.41,12l3.3,-3.29a1,1 0,0 0,0 -1.42z" />
</vector>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="64dp"
android:background="?android:selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeight"
android:paddingEnd="?android:attr/scrollbarSize">
<LinearLayout
android:id="@android:id/widget_frame"
android:layout_width="48dp"
android:layout_height="0dp"
android:gravity="center_vertical"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:textSize="16sp"
app:layout_constraintBottom_toTopOf="@android:id/summary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@android:id/widget_frame"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Delete browsing data category" />
<TextView
android:id="@android:id/summary"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@android:id/widget_frame"
app:layout_constraintTop_toBottomOf="@android:id/title"
app:layout_constraintVertical_chainStyle="packed"
tools:text="Delete browsing data summary" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -110,7 +110,7 @@
android:name="pastedText" android:name="pastedText"
android:defaultValue="@null" android:defaultValue="@null"
app:argType="string" app:argType="string"
app:nullable="true"/> app:nullable="true" />
</fragment> </fragment>
<fragment <fragment
@ -253,7 +253,7 @@
app:argType="string" /> app:argType="string" />
<action <action
android:id="@+id/action_bookmarkFragment_to_browserFragment" android:id="@+id/action_bookmarkFragment_to_browserFragment"
app:destination="@id/browserFragment"/> app:destination="@id/browserFragment" />
<action <action
android:id="@+id/action_bookmarkFragment_self" android:id="@+id/action_bookmarkFragment_self"
app:destination="@id/bookmarkFragment" /> app:destination="@id/bookmarkFragment" />
@ -359,6 +359,9 @@
<action <action
android:id="@+id/action_settingsFragment_to_accountProblemFragment" android:id="@+id/action_settingsFragment_to_accountProblemFragment"
app:destination="@id/accountProblemFragment" /> app:destination="@id/accountProblemFragment" />
<action
android:id="@+id/action_settingsFragment_to_deleteBrowsingDataOnQuitFragment"
app:destination="@id/deleteBrowsingDataOnQuitFragment" />
</fragment> </fragment>
<fragment <fragment
android:id="@+id/dataChoicesFragment" android:id="@+id/dataChoicesFragment"
@ -407,7 +410,7 @@
<fragment <fragment
android:id="@+id/pairFragment" android:id="@+id/pairFragment"
android:name="org.mozilla.fenix.settings.PairFragment" android:name="org.mozilla.fenix.settings.PairFragment"
android:label="@string/preferences_sync"></fragment> android:label="@string/preferences_sync" />
<fragment <fragment
android:id="@+id/aboutFragment" android:id="@+id/aboutFragment"
@ -566,4 +569,8 @@
android:name="strictMode" android:name="strictMode"
app:argType="boolean" /> app:argType="boolean" />
</fragment> </fragment>
<fragment
android:id="@+id/deleteBrowsingDataOnQuitFragment"
android:name="org.mozilla.fenix.settings.DeleteBrowsingDataOnQuitFragment"
android:label="DeleteBrowsingDataOnQuitFragment" />
</navigation> </navigation>

View File

@ -16,6 +16,15 @@
<string name="pref_key_data_choices" translatable="false">pref_key_data_choices</string> <string name="pref_key_data_choices" translatable="false">pref_key_data_choices</string>
<string name="pref_key_privacy_link" translatable="false">pref_key_privacy_link</string> <string name="pref_key_privacy_link" translatable="false">pref_key_privacy_link</string>
<string name="pref_key_delete_browsing_data" translatable="false">pref_key_delete_browsing_data</string> <string name="pref_key_delete_browsing_data" translatable="false">pref_key_delete_browsing_data</string>
<string name="pref_key_delete_browsing_data_on_quit_preference" translatable="false">pref_key_delete_browsing_data_on_quit_preference</string>
<string name="pref_key_delete_browsing_data_on_quit" translatable="false">pref_key_delete_browsing_data_on_quit</string>
<string name="pref_key_delete_open_tabs_on_quit" translatable="false">pref_key_delete_open_tabs_on_quit</string>
<string name="pref_key_delete_browsing_history_on_quit" translatable="false">pref_key_delete_browsing_history_on_quit</string>
<string name="pref_key_delete_cookies_on_quit" translatable="false">pref_key_delete_cookies_on_quit</string>
<string name="pref_key_delete_caches_on_quit" translatable="false">pref_key_delete_caches_on_quit</string>
<string name="pref_key_delete_permissions_on_quit" translatable="false">pref_key_delete_permissions_on_quit</string>
<string name="pref_key_delete_browsing_data_on_quit_categories" translatable="false">pref_key_delete_browsing_data_on_quit_categories</string>
<string name="pref_key_help" translatable="false">pref_key_help</string> <string name="pref_key_help" translatable="false">pref_key_help</string>
<string name="pref_key_rate" translatable="false">pref_key_rate</string> <string name="pref_key_rate" translatable="false">pref_key_rate</string>
<string name="pref_key_feedback" translatable="false">pref_key_feedback</string> <string name="pref_key_feedback" translatable="false">pref_key_feedback</string>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 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/. -->
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreference
android:defaultValue="false"
android:key="@string/pref_key_delete_browsing_data_on_quit"
android:summary="@string/preference_summary_delete_browsing_data_on_quit_2"
android:title="@string/preferences_delete_browsing_data_on_quit" />
<PreferenceCategory
android:dependency="@string/pref_key_delete_browsing_data_on_quit"
android:key="@string/pref_key_delete_browsing_data_on_quit_categories"
app:allowDividerAbove="false"
app:iconSpaceReserved="false">
<CheckBoxPreference
android:key="@string/pref_key_delete_open_tabs_on_quit"
android:layout="@layout/delete_browsing_category_checkbox"
android:title="@string/preferences_delete_browsing_data_tabs_title" />
<CheckBoxPreference
android:key="@string/pref_key_delete_browsing_history_on_quit"
android:layout="@layout/delete_browsing_category_checkbox"
android:title="@string/preferences_delete_browsing_data_on_quit_browsing_history" />
<CheckBoxPreference
android:key="@string/pref_key_delete_cookies_on_quit"
android:layout="@layout/delete_browsing_category_checkbox"
android:summary="@string/preferences_delete_browsing_data_cookies_subtitle"
android:title="@string/preferences_delete_browsing_data_cookies" />
<CheckBoxPreference
android:key="@string/pref_key_delete_caches_on_quit"
android:layout="@layout/delete_browsing_category_checkbox"
android:summary="@string/preferences_delete_browsing_data_cached_files_subtitle"
android:title="@string/preferences_delete_browsing_data_cached_files" />
<CheckBoxPreference
android:key="@string/pref_key_delete_permissions_on_quit"
android:layout="@layout/delete_browsing_category_checkbox"
android:title="@string/preferences_delete_browsing_data_site_permissions" />
</PreferenceCategory>
</androidx.preference.PreferenceScreen>

View File

@ -70,6 +70,11 @@
android:icon="@drawable/ic_delete" android:icon="@drawable/ic_delete"
android:key="@string/pref_key_delete_browsing_data" android:key="@string/pref_key_delete_browsing_data"
android:title="@string/preferences_delete_browsing_data" /> android:title="@string/preferences_delete_browsing_data" />
<androidx.preference.Preference
app:isPreferenceVisible="false"
android:icon="@drawable/ic_exit"
android:key="@string/pref_key_delete_browsing_data_on_quit_preference"
android:title="@string/preferences_delete_browsing_data_on_quit" />
<androidx.preference.Preference <androidx.preference.Preference
android:icon="@drawable/ic_data_collection" android:icon="@drawable/ic_data_collection"
android:key="@string/pref_key_data_choices" android:key="@string/pref_key_data_choices"

View File

@ -4,8 +4,10 @@
package org.mozilla.fenix.components.toolbar package org.mozilla.fenix.components.toolbar
import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.core.widget.NestedScrollView import androidx.core.widget.NestedScrollView
import androidx.lifecycle.LifecycleCoroutineScope
import androidx.navigation.NavController import androidx.navigation.NavController
import io.mockk.Runs import io.mockk.Runs
import io.mockk.every import io.mockk.every
@ -14,13 +16,18 @@ import io.mockk.mockk
import io.mockk.mockkStatic import io.mockk.mockkStatic
import io.mockk.verify import io.mockk.verify
import io.mockk.verifyOrder import io.mockk.verifyOrder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.ObsoleteCoroutinesApi import kotlinx.coroutines.ObsoleteCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
import mozilla.components.browser.session.SessionManager import mozilla.components.browser.session.SessionManager
import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.EngineView
import mozilla.components.feature.session.SessionUseCases import mozilla.components.feature.session.SessionUseCases
import mozilla.components.feature.tabs.TabsUseCases import mozilla.components.feature.tabs.TabsUseCases
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
@ -40,11 +47,14 @@ import org.mozilla.fenix.ext.toTab
import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.home.sessioncontrol.Tab
import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.home.sessioncontrol.TabCollection
import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior import org.mozilla.fenix.quickactionsheet.QuickActionSheetBehavior
import org.mozilla.fenix.utils.deleteAndQuit
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
@ObsoleteCoroutinesApi @ObsoleteCoroutinesApi
class DefaultBrowserToolbarControllerTest { class DefaultBrowserToolbarControllerTest {
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
private var context: HomeActivity = mockk(relaxed = true) private var context: HomeActivity = mockk(relaxed = true)
private var analytics: Analytics = mockk(relaxed = true) private var analytics: Analytics = mockk(relaxed = true)
private val browsingModeManager: BrowsingModeManager = mockk(relaxed = true) private val browsingModeManager: BrowsingModeManager = mockk(relaxed = true)
@ -56,14 +66,18 @@ class DefaultBrowserToolbarControllerTest {
private val getSupportUrl: () -> String = { "https://supportUrl.org" } private val getSupportUrl: () -> String = { "https://supportUrl.org" }
private val openInFenixIntent: Intent = mockk(relaxed = true) private val openInFenixIntent: Intent = mockk(relaxed = true)
private val currentSessionAsTab: Tab = mockk(relaxed = true) private val currentSessionAsTab: Tab = mockk(relaxed = true)
private val bottomSheetBehavior: QuickActionSheetBehavior<NestedScrollView> = mockk(relaxed = true) private val bottomSheetBehavior: QuickActionSheetBehavior<NestedScrollView> =
mockk(relaxed = true)
private val metrics: MetricController = mockk(relaxed = true) private val metrics: MetricController = mockk(relaxed = true)
private val sessionUseCases: SessionUseCases = mockk(relaxed = true) private val sessionUseCases: SessionUseCases = mockk(relaxed = true)
private val scope: LifecycleCoroutineScope = mockk(relaxed = true)
private lateinit var controller: DefaultBrowserToolbarController private lateinit var controller: DefaultBrowserToolbarController
@Before @Before
fun setUp() { fun setUp() {
Dispatchers.setMain(mainThreadSurrogate)
controller = DefaultBrowserToolbarController( controller = DefaultBrowserToolbarController(
context = context, context = context,
navController = navController, navController = navController,
@ -74,7 +88,8 @@ class DefaultBrowserToolbarControllerTest {
viewModel = viewModel, viewModel = viewModel,
getSupportUrl = getSupportUrl, getSupportUrl = getSupportUrl,
openInFenixIntent = openInFenixIntent, openInFenixIntent = openInFenixIntent,
bottomSheetBehavior = bottomSheetBehavior bottomSheetBehavior = bottomSheetBehavior,
scope = scope
) )
mockkStatic( mockkStatic(
@ -82,6 +97,11 @@ class DefaultBrowserToolbarControllerTest {
) )
every { any<Session>().toTab(any()) } returns currentSessionAsTab every { any<Session>().toTab(any()) } returns currentSessionAsTab
mockkStatic(
"org.mozilla.fenix.utils.DeleteAndQuitKt"
)
every { any<Context>().deleteAndQuit(any()) } just Runs
every { context.components.analytics } returns analytics every { context.components.analytics } returns analytics
every { analytics.metrics } returns metrics every { analytics.metrics } returns metrics
every { context.components.useCases.sessionUseCases } returns sessionUseCases 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 @Test
fun handleToolbarClick() { fun handleToolbarClick() {
every { currentSession.id } returns "1" every { currentSession.id } returns "1"
@ -208,7 +234,8 @@ class DefaultBrowserToolbarControllerTest {
@Test @Test
fun handleToolbarRequestDesktopOnPress() { fun handleToolbarRequestDesktopOnPress() {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = mockk(relaxed = true) val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(true) val item = ToolbarMenu.Item.RequestDesktop(true)
every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase
@ -226,7 +253,8 @@ class DefaultBrowserToolbarControllerTest {
@Test @Test
fun handleToolbarRequestDesktopOffPress() { fun handleToolbarRequestDesktopOffPress() {
val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase = mockk(relaxed = true) val requestDesktopSiteUseCase: SessionUseCases.RequestDesktopSiteUseCase =
mockk(relaxed = true)
val item = ToolbarMenu.Item.RequestDesktop(false) val item = ToolbarMenu.Item.RequestDesktop(false)
every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase every { sessionUseCases.requestDesktopSite } returns requestDesktopSiteUseCase
@ -309,7 +337,12 @@ class DefaultBrowserToolbarControllerTest {
verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE)) } verify { metrics.track(Event.BrowserMenuItemTapped(Event.BrowserMenuItemTapped.Item.REPORT_SITE_ISSUE)) }
verify { verify {
// Hardcoded URL because this function modifies the URL with an apply // 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, viewModel = viewModel,
getSupportUrl = getSupportUrl, getSupportUrl = getSupportUrl,
openInFenixIntent = openInFenixIntent, openInFenixIntent = openInFenixIntent,
bottomSheetBehavior = bottomSheetBehavior bottomSheetBehavior = bottomSheetBehavior,
scope = scope
) )
val sessionManager: SessionManager = mockk(relaxed = true) val sessionManager: SessionManager = mockk(relaxed = true)
@ -404,4 +438,13 @@ class DefaultBrowserToolbarControllerTest {
verify { context.startActivity(openInFenixIntent) } verify { context.startActivity(openInFenixIntent) }
verify { context.finish() } verify { context.finish() }
} }
@Test
fun handleToolbarQuitPress() {
val item = ToolbarMenu.Item.Quit
controller.handleToolbarItemInteraction(item)
verify { context.deleteAndQuit(scope) }
}
} }

View File

@ -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()
}
}
}

View File

@ -54,6 +54,51 @@ class SettingsTest {
assertFalse(settings.usePrivateMode) 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 @Test
fun defaultSearchEngineName() { fun defaultSearchEngineName() {
// When just created // When just created

View File

@ -76,11 +76,12 @@
<td>event</td> <td>event</td>
<td>A browser menu item was tapped</td> <td>A browser menu item was tapped</td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708">link</a></td> <td><a href="https://github.com/mozilla-mobile/fenix/pull/1214#issue-264756708">link</a></td>
<td><a href="https://github.com/mozilla-mobile/fenix/pull/5098#issuecomment-529658996">link</a></td>
<td> <td>
<table> <table>
<tr><td>item</td><td>A string containing the name of the item the user tapped. These items include: <tr><td>item</td><td>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, Settings, Library, Help, Desktop Site toggle on/off, Find in Page, New Tab,
Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button</td></tr> Private Tab, Share, Report Site Issue, Back/Forward button, Reload Button, Quit</td></tr>
</table> </table>
</td> </td>
<td>2020-03-01</td> <td>2020-03-01</td>