For #3106: Granular options for clearing user data
parent
eb26d951ab
commit
2b9efccfca
|
@ -329,6 +329,7 @@ dependencies {
|
||||||
|
|
||||||
implementation Deps.kotlin_stdlib
|
implementation Deps.kotlin_stdlib
|
||||||
implementation Deps.kotlin_coroutines
|
implementation Deps.kotlin_coroutines
|
||||||
|
testImplementation Deps.kotlin_coroutines_test
|
||||||
implementation Deps.androidx_appcompat
|
implementation Deps.androidx_appcompat
|
||||||
implementation Deps.androidx_constraintlayout
|
implementation Deps.androidx_constraintlayout
|
||||||
implementation Deps.androidx_coordinatorlayout
|
implementation Deps.androidx_coordinatorlayout
|
||||||
|
|
|
@ -36,4 +36,10 @@ object FeatureFlags {
|
||||||
* https://github.com/mozilla-mobile/fenix/issues/4431
|
* https://github.com/mozilla-mobile/fenix/issues/4431
|
||||||
*/
|
*/
|
||||||
const val mediaIntegration = true
|
const val mediaIntegration = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Granular data deletion provides additional choices on the Delete Browsing Data
|
||||||
|
* setting screen for cookies, cached images and files, and site permissions.
|
||||||
|
*/
|
||||||
|
val granularDataDeletion = nightly or debug
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/* 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.content.Context
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import mozilla.components.concept.engine.Engine
|
||||||
|
import mozilla.components.feature.tab.collections.TabCollection
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
|
interface DeleteBrowsingDataController {
|
||||||
|
suspend fun deleteTabs()
|
||||||
|
suspend fun deleteBrowsingData()
|
||||||
|
suspend fun deleteCollections(collections: List<TabCollection>)
|
||||||
|
suspend fun deleteCookies()
|
||||||
|
suspend fun deleteCachedFiles()
|
||||||
|
suspend fun deleteSitePermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DefaultDeleteBrowsingDataController(
|
||||||
|
val context: Context,
|
||||||
|
val coroutineContext: CoroutineContext = Dispatchers.Main
|
||||||
|
) : DeleteBrowsingDataController {
|
||||||
|
override suspend fun deleteTabs() {
|
||||||
|
withContext(coroutineContext) {
|
||||||
|
context.components.useCases.tabsUseCases.removeAllTabs.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteBrowsingData() {
|
||||||
|
withContext(coroutineContext) {
|
||||||
|
context.components.core.engine.clearData(Engine.BrowsingData.all())
|
||||||
|
}
|
||||||
|
context.components.core.historyStorage.deleteEverything()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCollections(collections: List<TabCollection>) {
|
||||||
|
while (context.components.core.tabCollectionStorage.getTabCollectionsCount() != collections.size) {
|
||||||
|
delay(DELAY_IN_MILLIS)
|
||||||
|
}
|
||||||
|
|
||||||
|
collections.forEach { context.components.core.tabCollectionStorage.removeCollection(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCookies() {
|
||||||
|
withContext(coroutineContext) {
|
||||||
|
context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.COOKIES))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteCachedFiles() {
|
||||||
|
withContext(coroutineContext) {
|
||||||
|
context.components.core.engine.clearData(
|
||||||
|
Engine.BrowsingData.select(Engine.BrowsingData.ALL_CACHES)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteSitePermissions() {
|
||||||
|
withContext(coroutineContext) {
|
||||||
|
context.components.core.engine.clearData(
|
||||||
|
Engine.BrowsingData.select(Engine.BrowsingData.ALL_SITE_SETTINGS)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
context.components.core.permissionStorage.deleteAllSitePermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val DELAY_IN_MILLIS = 500L
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,16 +15,17 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import androidx.paging.PagedList
|
||||||
|
import androidx.paging.toLiveData
|
||||||
import kotlinx.android.synthetic.main.fragment_delete_browsing_data.*
|
import kotlinx.android.synthetic.main.fragment_delete_browsing_data.*
|
||||||
import kotlinx.android.synthetic.main.fragment_delete_browsing_data.view.*
|
import kotlinx.android.synthetic.main.fragment_delete_browsing_data.view.*
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
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.Engine
|
import mozilla.components.feature.sitepermissions.SitePermissions
|
||||||
import mozilla.components.feature.tab.collections.TabCollection
|
import mozilla.components.feature.tab.collections.TabCollection
|
||||||
|
import org.mozilla.fenix.FeatureFlags
|
||||||
import org.mozilla.fenix.R
|
import org.mozilla.fenix.R
|
||||||
import org.mozilla.fenix.components.FenixSnackbar
|
import org.mozilla.fenix.components.FenixSnackbar
|
||||||
import org.mozilla.fenix.components.metrics.Event
|
import org.mozilla.fenix.components.metrics.Event
|
||||||
|
@ -34,6 +35,7 @@ import org.mozilla.fenix.ext.requireComponents
|
||||||
class DeleteBrowsingDataFragment : Fragment() {
|
class DeleteBrowsingDataFragment : Fragment() {
|
||||||
private lateinit var sessionObserver: SessionManager.Observer
|
private lateinit var sessionObserver: SessionManager.Observer
|
||||||
private var tabCollections: List<TabCollection> = listOf()
|
private var tabCollections: List<TabCollection> = listOf()
|
||||||
|
private lateinit var controller: DeleteBrowsingDataController
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -45,6 +47,8 @@ class DeleteBrowsingDataFragment : Fragment() {
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context!!)
|
||||||
|
|
||||||
sessionObserver = object : SessionManager.Observer {
|
sessionObserver = object : SessionManager.Observer {
|
||||||
override fun onSessionAdded(session: Session) = updateTabCount()
|
override fun onSessionAdded(session: Session) = updateTabCount()
|
||||||
override fun onSessionRemoved(session: Session) = updateTabCount()
|
override fun onSessionRemoved(session: Session) = updateTabCount()
|
||||||
|
@ -60,9 +64,9 @@ class DeleteBrowsingDataFragment : Fragment() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
view.open_tabs_item?.onCheckListener = { _ -> updateDeleteButton() }
|
getCheckboxes().forEach {
|
||||||
view.browsing_data_item?.onCheckListener = { _ -> updateDeleteButton() }
|
it.onCheckListener = { _ -> updateDeleteButton() }
|
||||||
view.collections_item?.onCheckListener = { _ -> updateDeleteButton() }
|
}
|
||||||
view.delete_data?.setOnClickListener {
|
view.delete_data?.setOnClickListener {
|
||||||
askToDelete()
|
askToDelete()
|
||||||
}
|
}
|
||||||
|
@ -75,10 +79,11 @@ class DeleteBrowsingDataFragment : Fragment() {
|
||||||
supportActionBar?.show()
|
supportActionBar?.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTabCount()
|
getCheckboxes().forEach {
|
||||||
updateHistoryCount()
|
it.visibility = View.VISIBLE
|
||||||
updateCollectionsCount()
|
}
|
||||||
updateDeleteButton()
|
|
||||||
|
updateItemCounts()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun askToDelete() {
|
private fun askToDelete() {
|
||||||
|
@ -100,15 +105,20 @@ class DeleteBrowsingDataFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deleteSelected() {
|
private fun deleteSelected() {
|
||||||
val openTabsChecked = view!!.open_tabs_item!!.isChecked
|
|
||||||
val browsingDataChecked = view!!.browsing_data_item!!.isChecked
|
|
||||||
val collectionsChecked = view!!.collections_item!!.isChecked
|
|
||||||
|
|
||||||
startDeletion()
|
startDeletion()
|
||||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
|
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
|
||||||
if (openTabsChecked) deleteTabs()
|
getCheckboxes().mapIndexed { i, v ->
|
||||||
if (browsingDataChecked) deleteBrowsingData()
|
if (v.isChecked) {
|
||||||
if (collectionsChecked) deleteCollections()
|
when (i) {
|
||||||
|
OPEN_TABS_INDEX -> controller.deleteTabs()
|
||||||
|
HISTORY_INDEX -> controller.deleteBrowsingData()
|
||||||
|
COLLECTIONS_INDEX -> controller.deleteCollections(tabCollections)
|
||||||
|
COOKIES_INDEX -> controller.deleteCookies()
|
||||||
|
CACHED_INDEX -> controller.deleteCachedFiles()
|
||||||
|
PERMS_INDEX -> controller.deleteSitePermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
launch(Dispatchers.Main) {
|
launch(Dispatchers.Main) {
|
||||||
finishDeletion()
|
finishDeletion()
|
||||||
|
@ -131,28 +141,34 @@ class DeleteBrowsingDataFragment : Fragment() {
|
||||||
delete_browsing_data_wrapper.isClickable = true
|
delete_browsing_data_wrapper.isClickable = true
|
||||||
delete_browsing_data_wrapper.alpha = ENABLED_ALPHA
|
delete_browsing_data_wrapper.alpha = ENABLED_ALPHA
|
||||||
|
|
||||||
listOf(open_tabs_item, browsing_data_item, collections_item).forEach {
|
getCheckboxes().forEach {
|
||||||
it.isChecked = false
|
it.isChecked = false
|
||||||
}
|
}
|
||||||
|
|
||||||
updateTabCount()
|
updateItemCounts()
|
||||||
updateHistoryCount()
|
|
||||||
updateCollectionsCount()
|
|
||||||
|
|
||||||
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_SHORT)
|
FenixSnackbar.make(view!!, FenixSnackbar.LENGTH_SHORT)
|
||||||
.setText(resources.getString(R.string.preferences_delete_browsing_data_snackbar))
|
.setText(resources.getString(R.string.preferences_delete_browsing_data_snackbar))
|
||||||
.show()
|
.show()
|
||||||
|
|
||||||
if (popAfter) viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
if (popAfter || FeatureFlags.granularDataDeletion) viewLifecycleOwner.lifecycleScope.launch(
|
||||||
|
Dispatchers.Main
|
||||||
|
) {
|
||||||
findNavController().popBackStack(R.id.homeFragment, false)
|
findNavController().popBackStack(R.id.homeFragment, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun updateItemCounts() {
|
||||||
|
updateTabCount()
|
||||||
|
updateHistoryCount()
|
||||||
|
updateCollectionsCount()
|
||||||
|
updateCookies()
|
||||||
|
updateCachedImagesAndFiles()
|
||||||
|
updateSitePermissions()
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateDeleteButton() {
|
private fun updateDeleteButton() {
|
||||||
val openTabs = view!!.open_tabs_item!!.isChecked
|
val enabled = getCheckboxes().any { it.isChecked }
|
||||||
val browsingData = view!!.browsing_data_item!!.isChecked
|
|
||||||
val collections = view!!.collections_item!!.isChecked
|
|
||||||
val enabled = openTabs || browsingData || collections
|
|
||||||
|
|
||||||
view?.delete_data?.isEnabled = enabled
|
view?.delete_data?.isEnabled = enabled
|
||||||
view?.delete_data?.alpha = if (enabled) ENABLED_ALPHA else DISABLED_ALPHA
|
view?.delete_data?.alpha = if (enabled) ENABLED_ALPHA else DISABLED_ALPHA
|
||||||
|
@ -206,30 +222,52 @@ class DeleteBrowsingDataFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteTabs() {
|
private fun updateCookies() {
|
||||||
withContext(Dispatchers.Main) {
|
// NO OP until we have GeckoView methods to count cookies
|
||||||
requireComponents.useCases.tabsUseCases.removeAllTabs.invoke()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteBrowsingData() {
|
private fun updateCachedImagesAndFiles() {
|
||||||
withContext(Dispatchers.Main) {
|
// NO OP until we have GeckoView methods to count cached images and files
|
||||||
requireComponents.core.engine.clearData(Engine.BrowsingData.all())
|
|
||||||
}
|
|
||||||
requireComponents.core.historyStorage.deleteEverything()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun deleteCollections() {
|
private fun updateSitePermissions() {
|
||||||
while (requireComponents.core.tabCollectionStorage.getTabCollectionsCount() != tabCollections.size) {
|
val liveData =
|
||||||
delay(DELAY_IN_MILLIS)
|
requireComponents.core.permissionStorage.getSitePermissionsPaged().toLiveData(1)
|
||||||
}
|
liveData.observe(
|
||||||
|
this,
|
||||||
|
object : Observer<PagedList<SitePermissions>> {
|
||||||
|
override fun onChanged(list: PagedList<SitePermissions>?) {
|
||||||
|
view?.site_permissions_item?.isEnabled = !list.isNullOrEmpty()
|
||||||
|
liveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
tabCollections.forEach { requireComponents.core.tabCollectionStorage.removeCollection(it) }
|
private fun getCheckboxes(): List<DeleteBrowsingDataItem> {
|
||||||
|
val fragmentView = view!!
|
||||||
|
val originalList = listOf(
|
||||||
|
fragmentView.open_tabs_item,
|
||||||
|
fragmentView.browsing_data_item,
|
||||||
|
fragmentView.collections_item
|
||||||
|
)
|
||||||
|
@Suppress("ConstantConditionIf")
|
||||||
|
val granularList = if (FeatureFlags.granularDataDeletion) listOf(
|
||||||
|
fragmentView.cookies_item,
|
||||||
|
fragmentView.cached_files_item,
|
||||||
|
fragmentView.site_permissions_item
|
||||||
|
) else emptyList()
|
||||||
|
return originalList + granularList
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ENABLED_ALPHA = 1f
|
private const val ENABLED_ALPHA = 1f
|
||||||
private const val DISABLED_ALPHA = 0.6f
|
private const val DISABLED_ALPHA = 0.6f
|
||||||
private const val DELAY_IN_MILLIS = 500L
|
|
||||||
|
private const val OPEN_TABS_INDEX = 0
|
||||||
|
private const val HISTORY_INDEX = 1
|
||||||
|
private const val COLLECTIONS_INDEX = 2
|
||||||
|
private const val COOKIES_INDEX = 3
|
||||||
|
private const val CACHED_INDEX = 4
|
||||||
|
private const val PERMS_INDEX = 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,20 +48,15 @@ class DeleteBrowsingDataItem @JvmOverloads constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
context.withStyledAttributes(attrs, R.styleable.DeleteBrowsingDataItem, defStyleAttr, 0) {
|
context.withStyledAttributes(attrs, R.styleable.DeleteBrowsingDataItem, defStyleAttr, 0) {
|
||||||
val iconId = getResourceId(
|
|
||||||
R.styleable.DeleteBrowsingDataItem_deleteBrowsingDataItemIcon,
|
|
||||||
R.drawable.library_icon_reading_list_circle_background
|
|
||||||
)
|
|
||||||
val titleId = getResourceId(
|
val titleId = getResourceId(
|
||||||
R.styleable.DeleteBrowsingDataItem_deleteBrowsingDataItemTitle,
|
R.styleable.DeleteBrowsingDataItem_deleteBrowsingDataItemTitle,
|
||||||
R.string.browser_menu_your_library
|
R.string.browser_menu_your_library
|
||||||
)
|
)
|
||||||
val subtitleId = getResourceId(
|
val subtitleId = getResourceId(
|
||||||
R.styleable.DeleteBrowsingDataItem_deleteBrowsingDataItemSubtitle,
|
R.styleable.DeleteBrowsingDataItem_deleteBrowsingDataItemSubtitle,
|
||||||
R.string.browser_menu_your_library
|
R.string.empty_string
|
||||||
)
|
)
|
||||||
|
|
||||||
icon.background = resources.getDrawable(iconId, context.theme)
|
|
||||||
title.text = resources.getString(titleId)
|
title.text = resources.getString(titleId)
|
||||||
subtitle.text = resources.getString(subtitleId)
|
subtitle.text = resources.getString(subtitleId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,17 +9,12 @@
|
||||||
android:layout_height="@dimen/library_item_height"
|
android:layout_height="@dimen/library_item_height"
|
||||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||||
|
|
||||||
<ImageView
|
<CheckBox
|
||||||
android:id="@+id/icon"
|
android:id="@+id/checkbox"
|
||||||
android:layout_width="@dimen/library_item_icon_height"
|
|
||||||
android:layout_height="@dimen/library_item_icon_height"
|
|
||||||
android:layout_marginStart="@dimen/library_item_icon_margin_horizontal"
|
|
||||||
android:layout_marginTop="@dimen/library_item_icon_margin_vertical"
|
|
||||||
android:layout_marginEnd="@dimen/library_item_icon_margin_horizontal"
|
|
||||||
android:layout_marginBottom="@dimen/library_item_icon_margin_vertical"
|
|
||||||
android:background="@drawable/library_icon_reading_list_circle_background"
|
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:importantForAccessibility="no"
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
@ -32,9 +27,9 @@
|
||||||
android:textAppearance="@style/ListItemTextStyle"
|
android:textAppearance="@style/ListItemTextStyle"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
app:layout_constraintStart_toEndOf="@id/icon"
|
app:layout_constraintStart_toEndOf="@id/checkbox"
|
||||||
app:layout_constraintEnd_toStartOf="@id/checkbox"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
tools:text="Open Tabs" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/subtitle"
|
android:id="@+id/subtitle"
|
||||||
|
@ -43,17 +38,7 @@
|
||||||
android:layout_marginStart="@dimen/library_item_icon_margin_horizontal"
|
android:layout_marginStart="@dimen/library_item_icon_margin_horizontal"
|
||||||
android:textAppearance="@style/SubtitleTextStyle"
|
android:textAppearance="@style/SubtitleTextStyle"
|
||||||
android:clickable="false"
|
android:clickable="false"
|
||||||
app:layout_constraintStart_toEndOf="@id/icon"
|
app:layout_constraintStart_toEndOf="@id/checkbox"
|
||||||
app:layout_constraintEnd_toStartOf="@id/checkbox"
|
app:layout_constraintTop_toBottomOf="@id/title"
|
||||||
app:layout_constraintTop_toBottomOf="@id/title" />
|
tools:text="2 Open Tabs" />
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/checkbox"
|
|
||||||
android:clickable="false"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</merge>
|
</merge>
|
||||||
|
|
|
@ -36,7 +36,6 @@
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
app:deleteBrowsingDataItemIcon="@drawable/ic_tab_circle_background"
|
|
||||||
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_tabs_title"
|
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_tabs_title"
|
||||||
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_tabs_subtitle" />
|
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_tabs_subtitle" />
|
||||||
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
||||||
|
@ -46,7 +45,6 @@
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
app:deleteBrowsingDataItemIcon="@drawable/library_icon_history_circle_background"
|
|
||||||
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_browsing_data_title"
|
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_browsing_data_title"
|
||||||
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_browsing_data_subtitle" />
|
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_browsing_data_subtitle" />
|
||||||
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
||||||
|
@ -56,9 +54,37 @@
|
||||||
android:background="?android:attr/selectableItemBackground"
|
android:background="?android:attr/selectableItemBackground"
|
||||||
android:clickable="true"
|
android:clickable="true"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
app:deleteBrowsingDataItemIcon="@drawable/ic_collections_circle_background"
|
|
||||||
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_collections_title"
|
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_collections_title"
|
||||||
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_collections_subtitle" />
|
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_collections_subtitle" />
|
||||||
|
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
||||||
|
android:id="@+id/cookies_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_cookies"
|
||||||
|
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_cookies_subtitle" />
|
||||||
|
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
||||||
|
android:id="@+id/cached_files_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_cached_files"
|
||||||
|
app:deleteBrowsingDataItemSubtitle="@string/preferences_delete_browsing_data_cached_files_subtitle" />
|
||||||
|
<org.mozilla.fenix.settings.DeleteBrowsingDataItem
|
||||||
|
android:id="@+id/site_permissions_item"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:deleteBrowsingDataItemTitle="@string/preferences_delete_browsing_data_site_permissions" />
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/delete_data"
|
android:id="@+id/delete_data"
|
||||||
style="@style/ThemeIndependentMaterialGreyButtonDestructive"
|
style="@style/ThemeIndependentMaterialGreyButtonDestructive"
|
||||||
|
|
|
@ -21,4 +21,7 @@
|
||||||
<!-- 1.0.1 strings -->
|
<!-- 1.0.1 strings -->
|
||||||
<!-- Bookmark deletion confirmation -->
|
<!-- Bookmark deletion confirmation -->
|
||||||
<string name="bookmark_deletion_confirmation" translatable="false">Are you sure you want to delete this bookmark?</string>
|
<string name="bookmark_deletion_confirmation" translatable="false">Are you sure you want to delete this bookmark?</string>
|
||||||
|
|
||||||
|
<!--suppress CheckTagEmptyBody This is a default value for places where we don't want a string set-->
|
||||||
|
<string name="empty_string" translatable="false"></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
/* 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.content.Context
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.mockk.Runs
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.just
|
||||||
|
import io.mockk.mockk
|
||||||
|
import io.mockk.verify
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
|
import kotlinx.coroutines.test.resetMain
|
||||||
|
import kotlinx.coroutines.test.runBlockingTest
|
||||||
|
import kotlinx.coroutines.test.setMain
|
||||||
|
import mozilla.components.concept.engine.Engine
|
||||||
|
import mozilla.components.feature.tab.collections.TabCollection
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mozilla.fenix.TestApplication
|
||||||
|
import org.mozilla.fenix.ext.components
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
@ObsoleteCoroutinesApi
|
||||||
|
@ExperimentalCoroutinesApi
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@Config(application = TestApplication::class)
|
||||||
|
class DefaultDeleteBrowsingDataControllerTest {
|
||||||
|
|
||||||
|
private val mainThreadSurrogate = newSingleThreadContext("UI thread")
|
||||||
|
|
||||||
|
private val context: Context = mockk(relaxed = true)
|
||||||
|
private lateinit var controller: DefaultDeleteBrowsingDataController
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
Dispatchers.setMain(mainThreadSurrogate)
|
||||||
|
|
||||||
|
every { context.components.core.engine.clearData(any()) } just Runs
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
Dispatchers.resetMain() // reset main dispatcher to the original Main dispatcher
|
||||||
|
mainThreadSurrogate.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteTabs() = runBlockingTest {
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)
|
||||||
|
every { context.components.useCases.tabsUseCases.removeAllTabs.invoke() } just Runs
|
||||||
|
|
||||||
|
controller.deleteTabs()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.components.useCases.tabsUseCases.removeAllTabs.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteBrowsingData() = runBlockingTest {
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)
|
||||||
|
every { context.components.core.historyStorage } returns mockk(relaxed = true)
|
||||||
|
|
||||||
|
controller.deleteBrowsingData()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.components.core.engine.clearData(Engine.BrowsingData.all())
|
||||||
|
context.components.core.historyStorage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteCollections() = runBlockingTest {
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)
|
||||||
|
|
||||||
|
val collections: List<TabCollection> = listOf(mockk(relaxed = true))
|
||||||
|
every { context.components.core.tabCollectionStorage.getTabCollectionsCount() } returns 1
|
||||||
|
|
||||||
|
controller.deleteCollections(collections)
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.components.core.tabCollectionStorage.removeCollection(collections[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteCookies() = runBlockingTest {
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)
|
||||||
|
|
||||||
|
controller.deleteCookies()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.COOKIES))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteCachedFiles() = runBlockingTest {
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)
|
||||||
|
|
||||||
|
controller.deleteCachedFiles()
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.ALL_CACHES))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun deleteSitePermissions() = runBlockingTest {
|
||||||
|
controller = DefaultDeleteBrowsingDataController(context, coroutineContext)
|
||||||
|
every { context.components.core.permissionStorage.deleteAllSitePermissions() } just Runs
|
||||||
|
|
||||||
|
launch(IO) {
|
||||||
|
controller.deleteSitePermissions()
|
||||||
|
}
|
||||||
|
|
||||||
|
verify {
|
||||||
|
context.components.core.engine.clearData(Engine.BrowsingData.select(Engine.BrowsingData.ALL_SITE_SETTINGS))
|
||||||
|
context.components.core.permissionStorage.deleteAllSitePermissions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
object Versions {
|
object Versions {
|
||||||
const val kotlin = "1.3.30"
|
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 android_gradle_plugin = "3.5.0"
|
||||||
const val newest_r8 = "ceaee94e172c6c057cc05e646f5324853fc5d4c5"
|
const val newest_r8 = "ceaee94e172c6c057cc05e646f5324853fc5d4c5"
|
||||||
const val rxAndroid = "2.1.0"
|
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 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_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 = "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 kotlin_coroutines_android = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines}"
|
||||||
|
|
||||||
const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"
|
const val allopen = "org.jetbrains.kotlin:kotlin-allopen:${Versions.kotlin}"
|
||||||
|
|
Loading…
Reference in New Issue