Using generated monochrome icon when actual icon is not available in the full drawable
Also simplifying the bitmap fetching path to return all relevant data in one-call
Bug: 299336510
Test: Verified that the icons are themed during drag and app launch
Flag: ENABLE_FORCED_MONO_ICON
Change-Id: Ib4c457e911e7b5616e8370d111cc68c5d6401fe6
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index e8c6ff9..7b27a71 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -16,13 +16,14 @@
package com.android.launcher3;
+import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
+
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
-import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ICON_BADGED;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT;
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_TYPE_MAIN;
-import android.annotation.TargetApi;
+import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Person;
@@ -33,6 +34,9 @@
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;
@@ -43,8 +47,10 @@
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;
@@ -59,6 +65,7 @@
import android.text.style.TtsSpan;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.Pair;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
@@ -68,10 +75,13 @@
import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
import androidx.core.graphics.ColorUtils;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
import com.android.launcher3.graphics.TintedDrawableSpan;
+import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.icons.ShortcutCachingLogic;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.ItemInfo;
@@ -571,103 +581,112 @@
}
/**
- * Returns the full drawable for info without any flattening or pre-processing.
+ * Returns the full drawable for info as multiple layers of AdaptiveIconDrawable. The second
+ * drawable in the Pair is the badge used with the icon.
*
- * @param shouldThemeIcon If true, will theme icons when applicable
- * @param outObj this is set to the internal data associated with {@code info},
- * eg {@link LauncherActivityInfo} or {@link ShortcutInfo}.
+ * @param useTheme If true, will theme icons when applicable
*/
- @TargetApi(Build.VERSION_CODES.TIRAMISU)
- public static Drawable getFullDrawable(Context context, ItemInfo info, int width, int height,
- boolean shouldThemeIcon, Object[] outObj, boolean[] outIsIconThemed) {
- Drawable icon = loadFullDrawableWithoutTheme(context, info, width, height, outObj);
- if (ATLEAST_T && icon instanceof AdaptiveIconDrawable && shouldThemeIcon) {
- AdaptiveIconDrawable aid = (AdaptiveIconDrawable) icon.mutate();
- Drawable mono = aid.getMonochrome();
- if (mono != null && Themes.isThemedIconEnabled(context)) {
- outIsIconThemed[0] = true;
- int[] colors = ThemedIconDrawable.getColors(context);
- mono = mono.mutate();
- mono.setTint(colors[1]);
- return new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
- }
- }
- return icon;
- }
-
- private static Drawable loadFullDrawableWithoutTheme(Context context, ItemInfo info,
- int width, int height, Object[] outObj) {
- ActivityContext activity = ActivityContext.lookupContext(context);
+ @SuppressLint("UseCompatLoadingForDrawables")
+ @Nullable
+ @WorkerThread
+ public static <T extends Context & ActivityContext> Pair<AdaptiveIconDrawable, Drawable>
+ getFullDrawable(T context, ItemInfo info, int width, int height, boolean useTheme) {
+ useTheme &= Themes.isThemedIconEnabled(context);
LauncherAppState appState = LauncherAppState.getInstance(context);
+ Drawable mainIcon = null;
+
+ Drawable badge = null;
+ if ((info instanceof ItemInfoWithIcon iiwi) && !iiwi.usingLowResIcon()) {
+ badge = iiwi.bitmap.getBadgeDrawable(context, useTheme);
+ }
+
if (info instanceof PendingAddShortcutInfo) {
ShortcutConfigActivityInfo activityInfo =
((PendingAddShortcutInfo) info).getActivityInfo(context);
- outObj[0] = activityInfo;
- return activityInfo.getFullResIcon(appState.getIconCache());
- }
- if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
+ mainIcon = activityInfo.getFullResIcon(appState.getIconCache());
+ } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
LauncherActivityInfo activityInfo = context.getSystemService(LauncherApps.class)
.resolveActivity(info.getIntent(), info.user);
- outObj[0] = activityInfo;
- return activityInfo == null ? null : LauncherAppState.getInstance(context)
- .getIconProvider().getIcon(
- activityInfo, activity.getDeviceProfile().inv.fillResIconDpi);
+ if (activityInfo == null) {
+ return null;
+ }
+ mainIcon = appState.getIconProvider().getIcon(
+ activityInfo, appState.getInvariantDeviceProfile().fillResIconDpi);
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- List<ShortcutInfo> si = ShortcutKey.fromItemInfo(info)
+ List<ShortcutInfo> siList = ShortcutKey.fromItemInfo(info)
.buildRequest(context)
.query(ShortcutRequest.ALL);
- if (si.isEmpty()) {
+ if (siList.isEmpty()) {
return null;
} else {
- outObj[0] = si.get(0);
- return ShortcutCachingLogic.getIcon(context, si.get(0),
+ ShortcutInfo si = siList.get(0);
+ mainIcon = ShortcutCachingLogic.getIcon(context, si,
appState.getInvariantDeviceProfile().fillResIconDpi);
+ // Only fetch badge if the icon is on workspace
+ if (info.id != ItemInfo.NO_ID && badge == null) {
+ badge = appState.getIconCache().getShortcutInfoBadge(si)
+ .newIcon(context, FLAG_THEMED);
+ }
}
} else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
FolderAdaptiveIcon icon = FolderAdaptiveIcon.createFolderAdaptiveIcon(
- activity, info.id, new Point(width, height));
+ context, info.id, new Point(width, height));
if (icon == null) {
return null;
}
- outObj[0] = icon;
- return icon;
- } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_SEARCH_ACTION
- && info instanceof ItemInfoWithIcon) {
- return ((ItemInfoWithIcon) info).bitmap.newIcon(context);
- } else {
+ mainIcon = icon;
+ badge = icon.getBadge();
+ }
+
+ if (mainIcon == null) {
return null;
}
- }
-
- /**
- * For apps icons and shortcut icons that have badges, this method creates a drawable that can
- * later on be rendered on top of the layers for the badges. For app icons, work profile badges
- * can only be applied. For deep shortcuts, when dragged from the pop up container, there's no
- * badge. When dragged from workspace or folder, it may contain app AND/OR work profile badge
- **/
- @TargetApi(Build.VERSION_CODES.O)
- public static Drawable getBadge(Context context, ItemInfo info, Object obj,
- boolean isIconThemed) {
- LauncherAppState appState = LauncherAppState.getInstance(context);
- if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
- boolean iconBadged = (info instanceof ItemInfoWithIcon)
- && (((ItemInfoWithIcon) info).runtimeStatusFlags & FLAG_ICON_BADGED) > 0;
- if ((info.id == ItemInfo.NO_ID && !iconBadged)
- || !(obj instanceof ShortcutInfo)) {
- // The item is not yet added on home screen.
- return new ColorDrawable(Color.TRANSPARENT);
- }
- ShortcutInfo si = (ShortcutInfo) obj;
- return LauncherAppState.getInstance(appState.getContext())
- .getIconCache().getShortcutInfoBadge(si).newIcon(context, FLAG_THEMED);
- } else if (info.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
- return ((FolderAdaptiveIcon) obj).getBadge();
+ AdaptiveIconDrawable result;
+ if (mainIcon instanceof AdaptiveIconDrawable aid) {
+ result = aid;
} else {
- return Process.myUserHandle().equals(info.user)
- ? new ColorDrawable(Color.TRANSPARENT)
- : context.getDrawable(isIconThemed
- ? R.drawable.ic_work_app_badge_themed : R.drawable.ic_work_app_badge);
+ // Wrap the main icon in AID
+ try (LauncherIcons li = LauncherIcons.obtain(context)) {
+ result = li.wrapToAdaptiveIcon(mainIcon);
+ }
}
+ if (result == null) {
+ return null;
+ }
+
+ // Inject monochrome 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);
+ }
+ }
+ if (mono != null) {
+ result = new AdaptiveIconDrawable(new ColorDrawable(colors[0]), mono);
+ }
+ }
+
+ if (badge == null) {
+ badge = Process.myUserHandle().equals(info.user)
+ ? new ColorDrawable(Color.TRANSPARENT)
+ : context.getDrawable(useTheme
+ ? R.drawable.ic_work_app_badge_themed
+ : R.drawable.ic_work_app_badge);
+ }
+ return Pair.create(result, badge);
}
public static float squaredHypot(float x, float y) {