tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 989e6a4ac9109403a85419051e54b73694a83a2f
parent b61e00261553f3a669536d22ba846f4df4c3945d
Author: mike a. <mavduevskiy@mozilla.com>
Date:   Tue, 18 Nov 2025 06:20:16 +0000

Bug 1955887 - Part 3: Preserve store state during configuration change r=android-reviewers,marcin,twhite

... while avoiding memory leaks.

Differential Revision: https://phabricator.services.mozilla.com/D271541

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconAction.kt | 7+++++++
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconMiddleware.kt | 13++++++++++---
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconReducer.kt | 1+
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelectionFragment.kt | 47+++++++++++++++++++++++++----------------------
Mmobile/android/fenix/app/src/test/java/org/mozilla/fenix/iconpicker/AppIconMiddlewareTest.kt | 18++++++++++++++++++
5 files changed, 61 insertions(+), 25 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconAction.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconAction.kt @@ -62,4 +62,11 @@ sealed interface SystemAction : AppIconAction { * The app icon update error snackbar was dismissed. */ data object SnackbarDismissed : SystemAction + + /** + * Signals a context update. For example, due to config change. + * + * @property appIconUpdater an interface used by [AppIconMiddleware] for changing the app icon. + */ + data class EnvironmentRehydrated(val appIconUpdater: AppIconUpdater) : SystemAction } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconMiddleware.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconMiddleware.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.iconpicker +import androidx.annotation.VisibleForTesting import mozilla.components.lib.state.Middleware import mozilla.components.lib.state.MiddlewareContext @@ -13,7 +14,8 @@ import mozilla.components.lib.state.MiddlewareContext * @property updateAppIcon A interface that updates the main activity alias with the newly selected one. */ class AppIconMiddleware( - val updateAppIcon: AppIconUpdater, + @get:VisibleForTesting + internal var updateAppIcon: AppIconUpdater, ) : Middleware<AppIconState, AppIconAction> { override fun invoke( @@ -32,12 +34,17 @@ class AppIconMiddleware( } } + is SystemAction.EnvironmentRehydrated -> { + updateAppIcon = action.appIconUpdater + } + + is UserAction.Dismissed, + is UserAction.Selected, is SystemAction.Applied, is SystemAction.DialogDismissed, is SystemAction.SnackbarDismissed, is SystemAction.UpdateFailed, - is UserAction.Dismissed, - is UserAction.Selected, + -> { // no-op } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconReducer.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/AppIconReducer.kt @@ -32,5 +32,6 @@ private fun AppIconState.handleSystemAction(action: SystemAction): AppIconState userSelectedAppIcon = null, snackbarState = AppIconSnackbarState.ApplyingNewIconError, ) + is SystemAction.EnvironmentRehydrated -> this } } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelectionFragment.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelectionFragment.kt @@ -13,13 +13,16 @@ import androidx.fragment.compose.content import androidx.navigation.fragment.findNavController import mozilla.components.support.base.feature.UserInteractionHandler import org.mozilla.fenix.R +import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.iconpicker.AppIconMiddleware import org.mozilla.fenix.iconpicker.AppIconRepository import org.mozilla.fenix.iconpicker.AppIconState import org.mozilla.fenix.iconpicker.AppIconStore +import org.mozilla.fenix.iconpicker.AppIconUpdater import org.mozilla.fenix.iconpicker.DefaultAppIconRepository import org.mozilla.fenix.iconpicker.DefaultPackageManagerWrapper +import org.mozilla.fenix.iconpicker.SystemAction import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.utils.ShortcutManagerWrapperDefault import org.mozilla.fenix.utils.ShortcutsUpdaterDefault @@ -44,37 +47,37 @@ class AppIconSelectionFragment : Fragment(), UserInteractionHandler { ) = content { FirefoxTheme { AppIconSelection( - store = AppIconStore( - initialState = AppIconState( - currentAppIcon = appIconRepository.selectedAppIcon, - groupedIconOptions = appIconRepository.groupedAppIcons, - ), - middleware = listOf( - AppIconMiddleware( - updateAppIcon = { newIcon, currentIcon -> - updateAppIcon( - currentAliasSuffix = currentIcon.aliasSuffix, - newAliasSuffix = newIcon.aliasSuffix, - ) - }, + store = StoreProvider.get(this) { + AppIconStore( + initialState = AppIconState( + currentAppIcon = appIconRepository.selectedAppIcon, + groupedIconOptions = appIconRepository.groupedAppIcons, ), - ), - ), + middleware = listOf( + AppIconMiddleware( + updateAppIcon = updateAppIcon(), + ), + ), + ) + }.also { + it.dispatch( + SystemAction.EnvironmentRehydrated( + appIconUpdater = updateAppIcon(), + ), + ) + }, ) } } - private fun updateAppIcon( - currentAliasSuffix: String, - newAliasSuffix: String, - ): Boolean { + private fun updateAppIcon(): AppIconUpdater = AppIconUpdater { newIcon, currentIcon -> with(requireContext()) { - return changeAppLauncherIcon( + changeAppLauncherIcon( packageManager = packageManager, shortcutManager = ShortcutManagerWrapperDefault(this), shortcutInfo = ShortcutsUpdaterDefault(this), - appAlias = ComponentName(this, "$packageName.$currentAliasSuffix"), - newAppAlias = ComponentName(this, "$packageName.$newAliasSuffix"), + appAlias = ComponentName(this, "$packageName.${currentIcon.aliasSuffix}"), + newAppAlias = ComponentName(this, "$packageName.${newIcon.aliasSuffix}"), ) } } diff --git a/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/iconpicker/AppIconMiddlewareTest.kt b/mobile/android/fenix/app/src/test/java/org/mozilla/fenix/iconpicker/AppIconMiddlewareTest.kt @@ -89,4 +89,22 @@ class AppIconMiddlewareTest { assertEquals(listOf(confirmAction, SystemAction.UpdateFailed), result) } + + @Test + fun `GIVEN EnvironmentRehydrated system action WHEN middleware is called THEN the new app icon updater replaces the old one`() { + val initialUpdater = AppIconUpdater { _, _ -> false } + val middleware = AppIconMiddleware(initialUpdater) + val store = AppIconStore( + initialState = AppIconState(), + reducer = { state, action -> + state + }, + middleware = listOf(middleware), + ) + val newUpdater = AppIconUpdater { _, _ -> false } + + store.dispatch(SystemAction.EnvironmentRehydrated(newUpdater)).joinBlocking() + + assertEquals(newUpdater, middleware.updateAppIcon) + } }