Generalizing monochrome icon into Theme icons

Bug: 381897614
Flag: EXEMPT refactor
Test: atest MonoIconThemeControllerTest
      atest MonoThemedBitmapTest

Change-Id: Iaa0cbe3523ee26a6d4f77e002d0cca90657f89b2
This commit is contained in:
Sunny Goyal
2024-12-02 13:56:33 -08:00
parent cfe6de65b4
commit 97eb832cd3
6 changed files with 210 additions and 84 deletions
+10 -28
View File
@@ -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) {
@@ -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<LauncherIcons> mPool;
private MonochromeIconFactory mMonochromeIconFactory;
protected LauncherIcons(Context context, int fillResIconDpi, int iconBitmapSize,
ConcurrentLinkedQueue<LauncherIcons> 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) {
@@ -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)
}
@@ -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)
}
}
}
@@ -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())
}
}
@@ -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;