commit 43c11dd06fa26bfd66aaa9b13f198b26ece1247d parent e784d5c2e5c52a287488f01fc1a2769dc99ae33c Author: mike a. <mavduevskiy@mozilla.com> Date: Tue, 25 Nov 2025 19:47:49 +0000 Bug 1983768 - Add a shortcut deletion warning for older devices r=android-reviewers,android-l10n-reviewers,flod,gmalekpour ... when changing the app icon. Android versions oldern than 10 remove shortcuts when the main activity alias changes. We should inform the user about the trade-offs of using the feature. Differential Revision: https://phabricator.services.mozilla.com/D270340 Diffstat:
5 files changed, 52 insertions(+), 12 deletions(-)
diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/FeatureFlags.kt @@ -4,8 +4,6 @@ package org.mozilla.fenix -import android.os.Build - /** * A single source for setting feature flags that are mostly based on build type. */ @@ -57,12 +55,4 @@ object FeatureFlags { * Enables the Mozilla Ads Client. */ const val MOZILLA_ADS_CLIENT_ENABLED = false - - /** - * Enables the app icon selection feature. - * The implementation of the feature is based on changing activity aliases, and on versions of Android older than 10 - * that also leads to removal of created shortcuts. We need to work out a good UX to avoid confusion and data loss. - * Tracking here: https://bugzilla.mozilla.org/show_bug.cgi?id=1983768 - */ - val APP_ICON_SELECTION = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q } diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelection.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/iconpicker/ui/AppIconSelection.kt @@ -86,10 +86,13 @@ private val GroupSpacerHeight = 8.dp * A composable that displays a list of app icon options. * * @param store A store for managing the app icon selection screen state. + * @param shortcutRemovalWarning Whether the user should be shown a warning that their Home screen + * shortcuts will be removed when changing the app icon. */ @Composable fun AppIconSelection( store: AppIconStore, + shortcutRemovalWarning: () -> Boolean, ) { val state by store.observeAsState(store.state) { it } val selectedIcon = state.userSelectedAppIcon ?: state.currentAppIcon @@ -133,6 +136,7 @@ fun AppIconSelection( when (val warning = state.warningDialogState) { is AppIconWarningDialog.Presenting -> RestartWarningDialog( + shortcutRemovalWarning = shortcutRemovalWarning, onConfirmClicked = { store.dispatch( UserAction.Confirmed( @@ -315,6 +319,7 @@ fun AppIcon( @Composable private fun RestartWarningDialog( + shortcutRemovalWarning: () -> Boolean, onConfirmClicked: () -> Unit, onDismissClicked: () -> Unit, onDismissed: () -> Unit, @@ -329,7 +334,11 @@ private fun RestartWarningDialog( text = { Text( text = stringResource( - id = R.string.restart_warning_dialog_body_2, + id = if (shortcutRemovalWarning()) { + R.string.restart_and_shortcuts_removal_warning_dialog_body + } else { + R.string.restart_warning_dialog_body_2 + }, stringResource(R.string.app_name), ), style = FirefoxTheme.typography.body2, @@ -366,6 +375,7 @@ private fun AppIconSelectionPreview() { ).groupedAppIcons, ), ), + shortcutRemovalWarning = { false }, ) } } @@ -407,6 +417,7 @@ private fun AppIconOptionWithSubtitlePrivatePreview() { private fun RestartWarningDialogPreview() { FirefoxTheme { RestartWarningDialog( + shortcutRemovalWarning = { false }, onConfirmClicked = {}, onDismissClicked = {}, onDismissed = {}, @@ -419,6 +430,33 @@ private fun RestartWarningDialogPreview() { private fun RestartWarningDialogPrivatePreview() { FirefoxTheme(theme = Theme.Private) { RestartWarningDialog( + shortcutRemovalWarning = { false }, + onConfirmClicked = {}, + onDismissClicked = {}, + onDismissed = {}, + ) + } +} + +@FlexibleWindowLightDarkPreview +@Composable +private fun ShortcutRemovalWarningDialogPreview() { + FirefoxTheme { + RestartWarningDialog( + shortcutRemovalWarning = { true }, + onConfirmClicked = {}, + onDismissClicked = {}, + onDismissed = {}, + ) + } +} + +@Preview +@Composable +private fun ShortcutRemovalWarningDialogPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + RestartWarningDialog( + shortcutRemovalWarning = { true }, onConfirmClicked = {}, onDismissClicked = {}, onDismissed = {}, 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 @@ -5,6 +5,7 @@ package org.mozilla.fenix.iconpicker.ui import android.content.ComponentName +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.ViewGroup @@ -60,6 +61,7 @@ class AppIconSelectionFragment : Fragment(), UserInteractionHandler { ), ) }, + shortcutRemovalWarning = { shouldWarnAboutShortcutRemoval() }, ) } } @@ -77,6 +79,14 @@ class AppIconSelectionFragment : Fragment(), UserInteractionHandler { } } + private fun shouldWarnAboutShortcutRemoval(): Boolean { + // Android versions older than 10 will remove existing shortcuts when activity alias changes, + // which is the underlying mechanics of changing the app icon on android. + val willRemoveShortcuts = Build.VERSION.SDK_INT < Build.VERSION_CODES.Q + val hasShortcuts = ShortcutManagerWrapperDefault(requireContext()).getPinnedShortcuts().isNotEmpty() + return willRemoveShortcuts && hasShortcuts + } + override fun onResume() { super.onResume() showToolbar(getString(R.string.preferences_app_icon)) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -497,7 +497,7 @@ class Settings( val appIconSelection by lazyFeatureFlagPreference( key = appContext.getPreferenceKey(R.string.pref_key_app_icon_selection_enabled), - featureFlag = FeatureFlags.APP_ICON_SELECTION, + featureFlag = true, default = { FxNimbus.features.appIconSelection.value().enabled }, ) diff --git a/mobile/android/fenix/app/src/main/res/values/strings.xml b/mobile/android/fenix/app/src/main/res/values/strings.xml @@ -967,6 +967,8 @@ <string name="restart_warning_dialog_title">Change app icon?</string> <!-- Message to warn users that applying an alternative app icon may require an app restart. Whether or not the app closes depends on what version of Android it is, so we use the word "may" instead of "will". %1$s is replaced by the brand name (e.g. Firefox). --> <string name="restart_warning_dialog_body_2">The app may close. Just tap your shiny new icon to reopen %1$s.</string> + <!-- Message to warn users that applying an alternative app icon may require an app restart and will remove existing shortcuts because of the OS limitations. Whether or not the app closes depends on what version of Android it is, so we use the word "may" instead of "will". %1$s is replaced by the brand name (e.g. Firefox). --> + <string name="restart_and_shortcuts_removal_warning_dialog_body">Changing the icon will remove any sites and shortcuts you’ve saved to your Home screen. \n\n%1$s may close. Tap your new icon to reopen.</string> <!-- Dialog button text for confirming the possible app restart and new icon selection--> <string name="restart_warning_dialog_button_positive_2">Change icon</string> <!-- Dialog button text for cancelling app icon change-->