From 97eb832cd3c08464b941be7a7a42bef4aba1c655 Mon Sep 17 00:00:00 2001 From: Sunny Goyal Date: Mon, 2 Dec 2024 13:56:33 -0800 Subject: [PATCH] Generalizing monochrome icon into Theme icons Bug: 381897614 Flag: EXEMPT refactor Test: atest MonoIconThemeControllerTest atest MonoThemedBitmapTest Change-Id: Iaa0cbe3523ee26a6d4f77e002d0cca90657f89b2 --- src/com/android/launcher3/Utilities.java | 38 ++--- .../launcher3/icons/LauncherIcons.java | 22 +-- .../folder/PreviewItemManagerTest.kt | 47 ++----- .../icons/mono/MonoIconThemeControllerTest.kt | 131 ++++++++++++++++++ .../icons/mono/MonoThemedBitmapTest.kt | 54 ++++++++ .../ui/workspace/ThemeIconsTest.java | 2 +- 6 files changed, 210 insertions(+), 84 deletions(-) create mode 100644 tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt create mode 100644 tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 71a2589dc7..ebcb5da814 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -16,8 +16,6 @@ package com.android.launcher3; -import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction; - import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN; import static com.android.launcher3.Flags.enableSmartspaceAsAWidget; import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED; @@ -36,9 +34,6 @@ import android.content.pm.LauncherApps; import android.content.pm.ShortcutInfo; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BlendMode; -import android.graphics.BlendModeColorFilter; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.LightingColorFilter; @@ -49,10 +44,8 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; import android.os.Build; import android.os.Build.VERSION_CODES; import android.os.DeadObjectException; @@ -85,7 +78,6 @@ import com.android.launcher3.graphics.TintedDrawableSpan; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.CacheableShortcutInfo; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.icons.ThemedIconDrawable; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.ItemInfoWithIcon; import com.android.launcher3.pm.ShortcutConfigActivityInfo; @@ -696,29 +688,19 @@ public final class Utilities { return null; } - // Inject monochrome icon drawable + // Inject theme icon drawable if (ATLEAST_T && useTheme) { - result.mutate(); - int[] colors = ThemedIconDrawable.getColors(context); - Drawable mono = result.getMonochrome(); - - if (mono != null) { - mono.setTint(colors[1]); - } else if (info instanceof ItemInfoWithIcon iiwi) { - // Inject a previously generated monochrome icon - Bitmap monoBitmap = iiwi.bitmap.getMono(); - if (monoBitmap != null) { - // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is - // preserved in constantState - mono = new BitmapDrawable(monoBitmap); - mono.setColorFilter(new BlendModeColorFilter(colors[1], BlendMode.SRC_IN)); - // Inset the drawable according to the AdaptiveIconDrawable layers - mono = new InsetDrawable(mono, getExtraInsetFraction() / 2); + try (LauncherIcons li = LauncherIcons.obtain(context)) { + if (li.getThemeController() != null) { + AdaptiveIconDrawable themed = li.getThemeController().createThemedAdaptiveIcon( + context, + result, + info instanceof ItemInfoWithIcon iiwi ? iiwi.bitmap : null); + if (themed != null) { + result = themed; + } } } - if (mono != null) { - result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono); - } } if (badge == null) { diff --git a/src/com/android/launcher3/icons/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java index 884d448b6c..839dfb70ea 100644 --- a/src/com/android/launcher3/icons/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -17,15 +17,13 @@ package com.android.launcher3.icons; import android.content.Context; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.Drawable; import android.os.UserHandle; import androidx.annotation.NonNull; -import com.android.launcher3.Flags; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.graphics.IconShape; +import com.android.launcher3.icons.mono.MonoIconThemeController; import com.android.launcher3.pm.UserCache; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.SafeCloseable; @@ -57,13 +55,13 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { private final ConcurrentLinkedQueue mPool; - private MonochromeIconFactory mMonochromeIconFactory; - protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize, ConcurrentLinkedQueue pool) { super(context, fillResIconDpi, iconBitmapSize, IconShape.INSTANCE.get(context).getShape().enableShapeDetection()); - mMonoIconEnabled = Themes.isThemedIconEnabled(context); + if (Themes.isThemedIconEnabled(context)) { + mThemeController = new MonoIconThemeController(); + } mPool = pool; } @@ -75,18 +73,6 @@ public class LauncherIcons extends BaseIconFactory implements AutoCloseable { mPool.add(this); } - @Override - protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) { - Drawable mono = super.getMonochromeDrawable(base); - if (mono != null || !Flags.forceMonochromeAppIcons()) { - return mono; - } - if (mMonochromeIconFactory == null) { - mMonochromeIconFactory = new MonochromeIconFactory(mIconBitmapSize); - } - return mMonochromeIconFactory.wrap(base); - } - @NonNull @Override protected UserIconInfo getUserInfo(@NonNull UserHandle user) { diff --git a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt index 111ffaa090..7f481b775b 100644 --- a/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt +++ b/tests/multivalentTests/src/com/android/launcher3/folder/PreviewItemManagerTest.kt @@ -18,6 +18,7 @@ package com.android.launcher3.folder import android.R import android.content.Context +import android.graphics.Bitmap import android.os.Process import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -26,9 +27,9 @@ import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import com.android.launcher3.LauncherPrefs.Companion.THEMED_ICONS import com.android.launcher3.LauncherPrefs.Companion.get import com.android.launcher3.graphics.PreloadIconDrawable -import com.android.launcher3.icons.BaseIconFactory import com.android.launcher3.icons.FastBitmapDrawable import com.android.launcher3.icons.UserBadgeDrawable +import com.android.launcher3.icons.mono.MonoThemedBitmap import com.android.launcher3.model.data.FolderInfo import com.android.launcher3.model.data.ItemInfo import com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED @@ -88,54 +89,26 @@ class PreviewItemManagerTest { // Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents() // Set first icon to be themed. - folderApps[0] - .bitmap - .setMonoIcon( + folderApps[0].bitmap.themedBitmap = + MonoThemedBitmap( folderApps[0].bitmap.icon, - BaseIconFactory( - context, - context.resources.configuration.densityDpi, - previewItemManager.mIconSize, - ), + Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), ) // Set second icon to be non-themed. - folderApps[1] - .bitmap - .setMonoIcon( - null, - BaseIconFactory( - context, - context.resources.configuration.densityDpi, - previewItemManager.mIconSize, - ), - ) + folderApps[1].bitmap.themedBitmap = null // Set third icon to be themed with badge. - folderApps[2] - .bitmap - .setMonoIcon( + folderApps[2].bitmap.themedBitmap = + MonoThemedBitmap( folderApps[2].bitmap.icon, - BaseIconFactory( - context, - context.resources.configuration.densityDpi, - previewItemManager.mIconSize, - ), + Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), ) folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) // Set fourth icon to be non-themed with badge. folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK)) - folderApps[3] - .bitmap - .setMonoIcon( - null, - BaseIconFactory( - context, - context.resources.configuration.densityDpi, - previewItemManager.mIconSize, - ), - ) + folderApps[3].bitmap.themedBitmap = null defaultThemedIcons = get(context).get(THEMED_ICONS) } diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt new file mode 100644 index 0000000000..4af564e009 --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoIconThemeControllerTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.icons.mono + +import android.graphics.Color +import android.graphics.drawable.AdaptiveIconDrawable +import android.graphics.drawable.ColorDrawable +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.SetFlagsRule +import android.platform.uiautomatorhelpers.DeviceHelpers.context +import android.util.DisplayMetrics +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.Flags +import com.android.launcher3.icons.BaseIconFactory +import com.android.launcher3.icons.BitmapInfo +import com.android.launcher3.util.LauncherMultivalentJUnit.Companion.isRunningInRobolectric +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assume.assumeFalse +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class MonoIconThemeControllerTest { + + @get:Rule val mSetFlagsRule = SetFlagsRule() + + private val iconFactory = BaseIconFactory(context, DisplayMetrics.DENSITY_MEDIUM, 30) + + @Test + fun `createThemedBitmap when mono drawable is present`() { + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED)) + assertNotNull( + MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory) + ) + } + + @Test + @DisableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS) + fun `createThemedBitmap when mono generation is disabled`() { + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null) + assertNull( + MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory) + ) + } + + @Test + @EnableFlags(Flags.FLAG_FORCE_MONOCHROME_APP_ICONS) + fun `createThemedBitmap when mono generation is enabled`() { + ensureBitmapSerializationSupported() + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null) + assertNotNull( + MonoIconThemeController().createThemedBitmap(icon, BitmapInfo.LOW_RES_INFO, iconFactory) + ) + } + + @Test + fun `decode bitmap after serialization valid data`() { + ensureBitmapSerializationSupported() + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED)) + val iconInfo = iconFactory.createBadgedIconBitmap(icon) + + val themeBitmap = + MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory)!! + assertNotNull( + MonoIconThemeController().decode(themeBitmap.serialize(), iconInfo, iconFactory) + ) + } + + @Test + fun `decode bitmap after serialization invalid data`() { + ensureBitmapSerializationSupported() + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED)) + val iconInfo = iconFactory.createBadgedIconBitmap(icon) + assertNull(MonoIconThemeController().decode(byteArrayOf(1, 1, 1, 1), iconInfo, iconFactory)) + } + + @Test + fun `createThemedAdaptiveIcon with monochrome drawable`() { + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED)) + assertNotNull(MonoIconThemeController().createThemedAdaptiveIcon(context, icon, null)) + } + + @Test + fun `createThemedAdaptiveIcon with bitmap info`() { + val icon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, ColorDrawable(Color.RED)) + val iconInfo = iconFactory.createBadgedIconBitmap(icon) + iconInfo.themedBitmap = + MonoIconThemeController().createThemedBitmap(icon, iconInfo, iconFactory) + + val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null) + assertNotNull( + MonoIconThemeController().createThemedAdaptiveIcon(context, nonMonoIcon, iconInfo) + ) + } + + @Test + fun `createThemedAdaptiveIcon invalid bitmap info`() { + val nonMonoIcon = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null, null) + assertNull( + MonoIconThemeController() + .createThemedAdaptiveIcon(context, nonMonoIcon, BitmapInfo.LOW_RES_INFO) + ) + } + + companion object { + + fun ensureBitmapSerializationSupported() { + // Robolectric doesn't support serializing 8-bit bitmaps + assumeFalse(isRunningInRobolectric) + } + } +} diff --git a/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt new file mode 100644 index 0000000000..32de9e9c9d --- /dev/null +++ b/tests/multivalentTests/src/com/android/launcher3/icons/mono/MonoThemedBitmapTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.icons.mono + +import android.graphics.Bitmap +import android.platform.uiautomatorhelpers.DeviceHelpers.context +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.launcher3.icons.BitmapInfo +import com.android.launcher3.icons.mono.MonoIconThemeControllerTest.Companion.ensureBitmapSerializationSupported +import org.junit.Assert.assertTrue +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class MonoThemedBitmapTest { + + @Test + fun `newDrawable returns valid drawable`() { + val bitmap = + MonoThemedBitmap( + Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), + Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), + ) + val d = bitmap.newDrawable(BitmapInfo.LOW_RES_INFO, context) + assertTrue(d is ThemedIconDrawable) + } + + @Test + fun `serialize returns valid bytes`() { + ensureBitmapSerializationSupported() + val bitmap = + MonoThemedBitmap( + Bitmap.createBitmap(10, 10, Bitmap.Config.ALPHA_8), + Bitmap.createBitmap(10, 10, Bitmap.Config.ARGB_8888), + ) + assertTrue(bitmap.serialize().isNotEmpty()) + } +} diff --git a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java index c623513f07..c852729be3 100644 --- a/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java +++ b/tests/src/com/android/launcher3/ui/workspace/ThemeIconsTest.java @@ -37,7 +37,7 @@ import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.allapps.AllAppsRecyclerView; import com.android.launcher3.celllayout.FavoriteItemsTransaction; -import com.android.launcher3.icons.ThemedIconDrawable; +import com.android.launcher3.icons.mono.ThemedIconDrawable; import com.android.launcher3.popup.ArrowPopup; import com.android.launcher3.util.BaseLauncherActivityTest; import com.android.launcher3.util.Executors;