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) {