commit 65e60da1aa0cdf74a3bf576434102b89ea006558 parent ade4bbd47beeb455b4fbb60f2cc4930ffff37a22 Author: mike a. <mavduevskiy@mozilla.com> Date: Tue, 6 Jan 2026 01:57:38 +0000 Bug 2002420 - Update search widgets with the alternative icon r=android-reviewers,gmalekpour 'ic_launcher_foreground' and alike are resources that, for the most part, are used in ActivityAlias'es in the manifest. Those icons have embedded paddings, to comply with the android OS requirements for app icons. 'ic_firefox' and alike are the visually same icons, but meant to be used within the UI of the app - they have no paddings, and can be customized to our liking. Differential Revision: https://phabricator.services.mozilla.com/D274078 Diffstat:
10 files changed, 76 insertions(+), 12 deletions(-)
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 @@ -10,10 +10,12 @@ import mozilla.components.lib.state.Store /** * A middleware for handling side-effects in response to [AppIconAction]s. * - * @param updateAppIcon A interface that updates the main activity alias with the newly selected one. + * @param updateAppIcon An interface that updates the main activity alias with the newly selected one. + * @param updateSearchWidgets An interface that updates the Firefox search widgets with the app icon. */ class AppIconMiddleware( private val updateAppIcon: AppIconUpdater, + private val updateSearchWidgets: SearchWidgetsUpdater, ) : Middleware<AppIconState, AppIconAction> { override fun invoke( @@ -36,10 +38,10 @@ class AppIconMiddleware( ) } } + is SystemAction.Applied -> updateSearchWidgets() is UserAction.Dismissed, is UserAction.Selected, - is SystemAction.Applied, is SystemAction.DialogDismissed, is SystemAction.SnackbarDismissed, is SystemAction.SnackbarShown, @@ -57,3 +59,12 @@ class AppIconMiddleware( fun interface AppIconUpdater : (AppIcon, AppIcon) -> Boolean { override fun invoke(old: AppIcon, new: AppIcon): Boolean } + +/** + * An interface for updating Firefox search widgets. + * + * The widgets display the app logo, that needs to be updated once the app icon changes. + */ +fun interface SearchWidgetsUpdater : () -> Unit { + override fun invoke() +} 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 @@ -4,6 +4,7 @@ package org.mozilla.fenix.iconpicker.ui +import android.appwidget.AppWidgetManager import android.content.ComponentName import android.os.Build import android.os.Bundle @@ -25,10 +26,12 @@ import org.mozilla.fenix.iconpicker.AppIconTelemetryMiddleware import org.mozilla.fenix.iconpicker.AppIconUpdater import org.mozilla.fenix.iconpicker.DefaultAppIconRepository import org.mozilla.fenix.iconpicker.DefaultPackageManagerWrapper +import org.mozilla.fenix.iconpicker.SearchWidgetsUpdater import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.utils.ShortcutManagerWrapperDefault import org.mozilla.fenix.utils.ShortcutsUpdaterDefault import org.mozilla.fenix.utils.changeAppLauncherIcon +import org.mozilla.gecko.search.SearchWidgetProvider /** * Fragment that displays a list of alternative app icons. @@ -58,6 +61,7 @@ class AppIconSelectionFragment : Fragment(), UserInteractionHandler { middleware = listOf( AppIconMiddleware( updateAppIcon = updateAppIcon(), + updateSearchWidgets = updateSearchWidgets(), ), AppIconTelemetryMiddleware(), ), @@ -81,6 +85,11 @@ class AppIconSelectionFragment : Fragment(), UserInteractionHandler { } } + private fun updateSearchWidgets(): SearchWidgetsUpdater = SearchWidgetsUpdater { + val appWidgetManager = AppWidgetManager.getInstance(requireContext()) + SearchWidgetProvider.updateAllWidgets(requireContext(), appWidgetManager) + } + 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. diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/gecko/search/SearchWidgetProvider.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/gecko/search/SearchWidgetProvider.kt @@ -24,6 +24,8 @@ import org.mozilla.fenix.IntentReceiverActivity import org.mozilla.fenix.R import org.mozilla.fenix.ext.settings import org.mozilla.fenix.home.intent.StartSearchIntentProcessor +import org.mozilla.fenix.iconpicker.DefaultAppIconRepository +import org.mozilla.fenix.iconpicker.DefaultPackageManagerWrapper import org.mozilla.fenix.utils.IntentUtils import org.mozilla.fenix.widget.VoiceSearchActivity import org.mozilla.fenix.widget.VoiceSearchActivity.Companion.SPEECH_PROCESSING @@ -164,10 +166,14 @@ class SearchWidgetProvider : AppWidgetProvider() { } private fun RemoteViews.setIcon(context: Context) { + val repository = DefaultAppIconRepository( + packageManager = DefaultPackageManagerWrapper(context.packageManager), + packageName = context.packageName, + ) // gradient color available for android:fillColor only on SDK 24+ setImageViewResource( R.id.button_search_widget_new_tab_icon, - R.drawable.ic_launcher_foreground, + repository.selectedAppIcon.iconForegroundId, ) val appName = context.getString(R.string.app_name) diff --git a/mobile/android/fenix/app/src/main/res/layout/search_widget_extra_small_v1.xml b/mobile/android/fenix/app/src/main/res/layout/search_widget_extra_small_v1.xml @@ -14,9 +14,10 @@ android:id="@+id/button_search_widget_new_tab_icon" android:layout_width="50dp" android:layout_height="50dp" + android:padding="12dp" android:layout_gravity="center" android:contentDescription="@string/search_widget_content_description_2" android:scaleType="centerInside" - tools:src="@drawable/ic_launcher_foreground" /> + tools:src="@drawable/ic_firefox" /> </FrameLayout> diff --git a/mobile/android/fenix/app/src/main/res/layout/search_widget_extra_small_v2.xml b/mobile/android/fenix/app/src/main/res/layout/search_widget_extra_small_v2.xml @@ -14,8 +14,9 @@ android:id="@+id/button_search_widget_new_tab_icon" android:layout_width="50dp" android:layout_height="50dp" + android:padding="12dp" android:layout_gravity="center" android:contentDescription="@string/search_widget_content_description_2" android:scaleType="centerInside" - tools:src="@drawable/ic_launcher_foreground" /> + tools:src="@drawable/ic_firefox" /> </FrameLayout> diff --git a/mobile/android/fenix/app/src/main/res/layout/search_widget_large.xml b/mobile/android/fenix/app/src/main/res/layout/search_widget_large.xml @@ -14,6 +14,7 @@ android:id="@+id/button_search_widget_new_tab_icon" android:layout_width="50dp" android:layout_height="50dp" + android:padding="12dp" android:layout_alignParentStart="true" android:contentDescription="@string/search_widget_content_description_2" android:scaleType="centerInside" /> diff --git a/mobile/android/fenix/app/src/main/res/layout/search_widget_medium.xml b/mobile/android/fenix/app/src/main/res/layout/search_widget_medium.xml @@ -15,6 +15,7 @@ android:contentDescription="@string/search_widget_content_description_2" android:layout_width="50dp" android:layout_height="50dp" + android:padding="12dp" android:layout_alignParentStart="true" android:scaleType="centerInside" /> diff --git a/mobile/android/fenix/app/src/main/res/layout/search_widget_small.xml b/mobile/android/fenix/app/src/main/res/layout/search_widget_small.xml @@ -13,6 +13,7 @@ android:id="@+id/button_search_widget_new_tab_icon" android:layout_width="50dp" android:layout_height="50dp" + android:padding="12dp" android:contentDescription="@string/search_widget_content_description_2" android:scaleType="centerInside" /> diff --git a/mobile/android/fenix/app/src/main/res/layout/search_widget_small_no_mic.xml b/mobile/android/fenix/app/src/main/res/layout/search_widget_small_no_mic.xml @@ -14,6 +14,7 @@ android:id="@+id/button_search_widget_new_tab_icon" android:layout_width="40dp" android:layout_height="40dp" + android:padding="12dp" android:layout_gravity="center" android:contentDescription="@string/search_widget_content_description_2" /> </FrameLayout> 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 @@ -19,11 +19,14 @@ class AppIconMiddlewareTest { val newIcon = AppIcon.AppRetro2004 var updatedCurrentIcon: AppIcon? = null var updatedNewIcon: AppIcon? = null - val middleware = AppIconMiddleware { newIcon, currentIcon -> - updatedNewIcon = newIcon - updatedCurrentIcon = currentIcon - true - } + val middleware = AppIconMiddleware( + updateAppIcon = { newIcon, currentIcon -> + updatedNewIcon = newIcon + updatedCurrentIcon = currentIcon + true + }, + updateSearchWidgets = {}, + ) val store = AppIconStore( initialState = AppIconState( currentAppIcon = currentIcon, @@ -43,7 +46,10 @@ class AppIconMiddlewareTest { fun `WHEN updateAppIcon call is successful THEN the middleware dispatches the Applied system action to the store`() { val currentIcon = AppIcon.AppDefault val newIcon = AppIcon.AppRetro2004 - val middleware = AppIconMiddleware { _, _ -> true } + val middleware = AppIconMiddleware( + updateAppIcon = { _, _ -> true }, + updateSearchWidgets = {}, + ) val result = mutableListOf<AppIconAction>() val store = AppIconStore( initialState = AppIconState( @@ -68,7 +74,10 @@ class AppIconMiddlewareTest { fun `WHEN updateAppIcon call returns with an a failure THEN the middleware dispatches the UpdateFailed system action to the store`() { val currentIcon = AppIcon.AppDefault val newIcon = AppIcon.AppRetro2004 - val middleware = AppIconMiddleware { _, _ -> false } + val middleware = AppIconMiddleware( + updateAppIcon = { _, _ -> false }, + updateSearchWidgets = {}, + ) val result = mutableListOf<AppIconAction>() val store = AppIconStore( initialState = AppIconState( @@ -88,4 +97,27 @@ class AppIconMiddlewareTest { assertEquals(listOf(confirmAction, SystemAction.UpdateFailed(oldIcon = currentIcon, newIcon = newIcon)), result) } + + @Test + fun `WHEN the store receives SystemAction Applied THEN the middleware calls the updateWidgets interface`() { + var updateSearchWidgetsCalled = false + val middleware = AppIconMiddleware( + updateAppIcon = { _, _ -> false }, + updateSearchWidgets = { + updateSearchWidgetsCalled = true + }, + ) + val store = AppIconStore( + initialState = AppIconState( + currentAppIcon = AppIcon.AppPixelated, + userSelectedAppIcon = null, + groupedIconOptions = mapOf(), + ), + middleware = listOf(middleware), + ) + + store.dispatch(SystemAction.Applied(AppIcon.AppPixelated)) + + assertTrue(updateSearchWidgetsCalled) + } }