diff --git a/app/src/main/java/org/mozilla/fenix/components/Core.kt b/app/src/main/java/org/mozilla/fenix/components/Core.kt index e36f224f7..4020585d9 100644 --- a/app/src/main/java/org/mozilla/fenix/components/Core.kt +++ b/app/src/main/java/org/mozilla/fenix/components/Core.kt @@ -5,6 +5,7 @@ package org.mozilla.fenix.components import GeckoProvider +import android.app.Application import android.content.Context import android.content.res.Configuration import io.sentry.Sentry @@ -116,6 +117,11 @@ class Core(private val context: Context) { */ val customTabsStore by lazy { CustomTabsServiceStore() } + /** + * The [PendingSessionDeletionManager] maintains a set of sessionIds that are marked for deletion + */ + val pendingSessionDeletionManager by lazy { PendingSessionDeletionManager(context as Application) } + /** * The session manager component provides access to a centralized registry of * all browser sessions (i.e. tabs). It is initialized here to persist and restore @@ -142,6 +148,12 @@ class Core(private val context: Context) { ) } + pendingSessionDeletionManager.getSessionsToDelete(context).forEach { + sessionManager.findSessionById(it)?.let { session -> + sessionManager.remove(session) + } + } + // Now that we have restored our previous state (if there's one) let's setup auto saving the state while // the app is used. sessionStorage.autoSave(sessionManager) @@ -277,13 +289,13 @@ class Core(private val context: Context) { } private fun geCustomCookiePolicy(): CookiePolicy { - return when (context.settings().blockCookiesSelectionInCustomTrackingProtection) { - "all" -> CookiePolicy.ACCEPT_NONE - "social" -> CookiePolicy.ACCEPT_NON_TRACKERS - "unvisited" -> CookiePolicy.ACCEPT_VISITED - "third-party" -> CookiePolicy.ACCEPT_ONLY_FIRST_PARTY - else -> CookiePolicy.ACCEPT_NONE - } + return when (context.settings().blockCookiesSelectionInCustomTrackingProtection) { + "all" -> CookiePolicy.ACCEPT_NONE + "social" -> CookiePolicy.ACCEPT_NON_TRACKERS + "unvisited" -> CookiePolicy.ACCEPT_VISITED + "third-party" -> CookiePolicy.ACCEPT_ONLY_FIRST_PARTY + else -> CookiePolicy.ACCEPT_NONE + } } private fun getCustomTrackingCategories(): Array { diff --git a/app/src/main/java/org/mozilla/fenix/components/PendingSessionDeletionManager.kt b/app/src/main/java/org/mozilla/fenix/components/PendingSessionDeletionManager.kt new file mode 100644 index 000000000..4674af231 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/PendingSessionDeletionManager.kt @@ -0,0 +1,71 @@ +/* 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.components + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.os.Bundle +import org.mozilla.fenix.ext.settings + +class PendingSessionDeletionManager(application: Application) : + Application.ActivityLifecycleCallbacks { + + private val sessionIdsPendingDeletion = mutableSetOf() + + init { + application.registerActivityLifecycleCallbacks(this) + } + + fun addSession(sessionId: String) { + sessionIdsPendingDeletion.add(sessionId) + } + + fun removeSession(sessionId: String) { + sessionIdsPendingDeletion.remove(sessionId) + } + + fun getSessionsToDelete(context: Context): Set { + return context.settings().preferences.getStringSet( + PREF_KEY, + setOf() + ) ?: setOf() + } + + override fun onActivityPaused(activity: Activity?) { + activity?.settings()?.preferences?.edit()?.putStringSet( + PREF_KEY, + sessionIdsPendingDeletion + )?.apply() + } + + override fun onActivityResumed(p0: Activity?) { + /* no-op */ + } + + override fun onActivityStarted(p0: Activity?) { + /* no-op */ + } + + override fun onActivityDestroyed(p0: Activity?) { + /* no-op */ + } + + override fun onActivitySaveInstanceState(p0: Activity?, p1: Bundle?) { + /* no-op */ + } + + override fun onActivityStopped(p0: Activity?) { + /* no-op */ + } + + override fun onActivityCreated(p0: Activity?, p1: Bundle?) { + /* no-op */ + } + + companion object { + private const val PREF_KEY = "pref_key_session_id_set_to_delete" + } +} diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index cb1f9195c..236970e04 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -202,7 +202,7 @@ class HomeFragment : Fragment() { registerCollectionStorageObserver = ::registerCollectionStorageObserver, scrollToTheTop = ::scrollToTheTop, showDeleteCollectionPrompt = ::showDeleteCollectionPrompt, - openSettingsScreen = :: openSettingsScreen + openSettingsScreen = ::openSettingsScreen ) ) updateLayout(view) @@ -277,7 +277,8 @@ class HomeFragment : Fragment() { // TODO remove when viewLifecycleOwner is fixed val context = context ?: return@launch - val iconSize = context.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size) + val iconSize = + context.resources.getDimensionPixelSize(R.dimen.preference_icon_drawable_size) val searchEngine = context.components.search.provider.getDefaultEngine(context) val searchIcon = BitmapDrawable(context.resources, searchEngine.icon) @@ -336,7 +337,8 @@ class HomeFragment : Fragment() { if (onboarding.userHasBeenOnboarded()) { homeFragmentStore.dispatch( - HomeFragmentAction.ModeChange(Mode.fromBrowsingMode(newMode))) + HomeFragmentAction.ModeChange(Mode.fromBrowsingMode(newMode)) + ) } } } @@ -355,12 +357,14 @@ class HomeFragment : Fragment() { val context = requireContext() val components = context.components - homeFragmentStore.dispatch(HomeFragmentAction.Change( - collections = components.core.tabCollectionStorage.cachedTabCollections, - mode = currentMode.getCurrentMode(), - tabs = getListOfSessions().toTabs(), - topSites = components.core.topSiteStorage.cachedTopSites - )) + homeFragmentStore.dispatch( + HomeFragmentAction.Change( + collections = components.core.tabCollectionStorage.cachedTabCollections, + mode = currentMode.getCurrentMode(), + tabs = getListOfSessions().toTabs(), + topSites = components.core.topSiteStorage.cachedTopSites + ) + ) requireComponents.backgroundServices.accountManager.register(currentMode, owner = this) requireComponents.backgroundServices.accountManager.register(object : AccountObserver { @@ -377,7 +381,8 @@ class HomeFragment : Fragment() { }, owner = this) if (context.settings().showPrivateModeContextualFeatureRecommender && - browsingModeManager.mode.isPrivate) { + browsingModeManager.mode.isPrivate + ) { recommendPrivateBrowsingShortcut() } @@ -517,7 +522,8 @@ class HomeFragment : Fragment() { // Otherwise, we will encounter an activity token error. privateBrowsingButton.post { privateBrowsingRecommend.showAsDropDown( - privateBrowsingButton, 0, CFR_Y_OFFSET, Gravity.TOP or Gravity.END) + privateBrowsingButton, 0, CFR_Y_OFFSET, Gravity.TOP or Gravity.END + ) } } } @@ -528,7 +534,9 @@ class HomeFragment : Fragment() { homeFragmentStore.dispatch( HomeFragmentAction.ModeChange( mode = currentMode.getCurrentMode(), - tabs = getListOfSessions().toTabs())) + tabs = getListOfSessions().toTabs() + ) + ) } } @@ -633,10 +641,16 @@ class HomeFragment : Fragment() { private fun removeAllTabsWithUndo(listOfSessionsToDelete: Sequence, private: Boolean) { homeFragmentStore.dispatch(HomeFragmentAction.TabsChange(emptyList())) + listOfSessionsToDelete.forEach { + requireComponents.core.pendingSessionDeletionManager.addSession( + it.id + ) + } val deleteOperation: (suspend () -> Unit) = { listOfSessionsToDelete.forEach { sessionManager.remove(it) + requireComponents.core.pendingSessionDeletionManager.removeSession(it.id) } } deleteAllSessionsJob = deleteOperation @@ -651,6 +665,11 @@ class HomeFragment : Fragment() { view!!, snackbarMessage, getString(R.string.snackbar_deleted_undo), { + listOfSessionsToDelete.forEach { + requireComponents.core.pendingSessionDeletionManager.removeSession( + it.id + ) + } if (private) { requireComponents.analytics.metrics.track(Event.PrivateBrowsingSnackbarUndoTapped) } @@ -664,11 +683,13 @@ class HomeFragment : Fragment() { private fun removeTabWithUndo(sessionId: String, private: Boolean) { val sessionManager = requireComponents.core.sessionManager + requireComponents.core.pendingSessionDeletionManager.addSession(sessionId) val deleteOperation: (suspend () -> Unit) = { sessionManager.findSessionById(sessionId) ?.let { session -> pendingSessionDeletion = null sessionManager.remove(session) + requireComponents.core.pendingSessionDeletionManager.removeSession(sessionId) } } @@ -684,6 +705,7 @@ class HomeFragment : Fragment() { view!!, snackbarMessage, getString(R.string.snackbar_deleted_undo), { + requireComponents.core.pendingSessionDeletionManager.removeSession(sessionId) pendingSessionDeletion = null emitSessionChanges() }, @@ -777,8 +799,12 @@ class HomeFragment : Fragment() { border?.visibility = View.GONE } - override fun onAnimationStart(animation: Animator?) { /* noop */ } - override fun onAnimationRepeat(animation: Animator?) { /* noop */ } + override fun onAnimationStart(animation: Animator?) { /* noop */ + } + + override fun onAnimationRepeat(animation: Animator?) { /* noop */ + } + override fun onAnimationEnd(animation: Animator?) { border?.animate()?.alpha(0.0F)?.setStartDelay(ANIM_ON_SCREEN_DELAY) ?.setDuration(FADE_ANIM_DURATION)