Snap for 12763142 from 168514eb3f8cd694233262d9ae3419ea736dcbaa to 25Q1-release

Change-Id: Ib8b3d4dbc238a677f90cb90db202c45687933441
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index ad062d2..f3f9d1e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -87,7 +87,7 @@
     protected final int mFullResIconDpi;
     protected final int mIconBitmapSize;
 
-    protected boolean mMonoIconEnabled;
+    protected IconThemeController mThemeController;
 
     @Nullable
     private IconNormalizer mNormalizer;
@@ -144,6 +144,11 @@
         return mNormalizer;
     }
 
+    @Nullable
+    public IconThemeController getThemeController() {
+        return mThemeController;
+    }
+
     public int getFullResIconDpi() {
         return mFullResIconDpi;
     }
@@ -237,30 +242,13 @@
 
         if (adaptiveIcon instanceof BitmapInfo.Extender extender) {
             info = extender.getExtendedInfo(bitmap, color, this, scale[0]);
-        } else if (IconProvider.ATLEAST_T && mMonoIconEnabled) {
-            Drawable mono = getMonochromeDrawable(adaptiveIcon);
-            if (mono != null) {
-                info.setMonoIcon(createIconBitmap(mono, scale[0], MODE_ALPHA), this);
-            }
+        } else if (IconProvider.ATLEAST_T && mThemeController != null && adaptiveIcon != null) {
+            info.setThemedBitmap(mThemeController.createThemedBitmap(adaptiveIcon, info, this));
         }
         info = info.withFlags(getBitmapFlagOp(options));
         return info;
     }
 
-    /**
-     * Returns a monochromatic version of the given drawable or null, if it is not supported
-     *
-     * @param base the original icon
-     */
-    @TargetApi(Build.VERSION_CODES.TIRAMISU)
-    protected Drawable getMonochromeDrawable(AdaptiveIconDrawable base) {
-        Drawable mono = base.getMonochrome();
-        if (mono != null) {
-            return new ClippedMonoDrawable(mono);
-        }
-        return null;
-    }
-
     @NonNull
     public FlagOp getBitmapFlagOp(@Nullable IconOptions options) {
         FlagOp op = FlagOp.NO_OP;
@@ -386,7 +374,7 @@
     }
 
     @NonNull
-    protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale,
+    public Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale,
             @BitmapGenerationMode int bitmapGenerationMode) {
         final int size = mIconBitmapSize;
         final Bitmap bitmap;
@@ -610,26 +598,6 @@
         }
     }
 
-    protected static class ClippedMonoDrawable extends InsetDrawable {
-
-        @NonNull
-        private final AdaptiveIconDrawable mCrop;
-
-        public ClippedMonoDrawable(@Nullable final Drawable base) {
-            super(base, -getExtraInsetFraction());
-            mCrop = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            mCrop.setBounds(getBounds());
-            int saveCount = canvas.save();
-            canvas.clipPath(mCrop.getIconMask());
-            super.draw(canvas);
-            canvas.restoreToCount(saveCount);
-        }
-    }
-
     private static class CenterTextDrawable extends ColorDrawable {
 
         @NonNull
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
index 2767e12..480061a 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapInfo.java
@@ -60,8 +60,7 @@
     public final int color;
 
     @Nullable
-    protected Bitmap mMono;
-    protected Bitmap mWhiteShadowLayer;
+    private ThemedBitmap mThemedBitmap;
 
     public @BitmapInfoFlags int flags;
     private BitmapInfo badgeInfo;
@@ -90,8 +89,7 @@
     }
 
     protected BitmapInfo copyInternalsTo(BitmapInfo target) {
-        target.mMono = mMono;
-        target.mWhiteShadowLayer = mWhiteShadowLayer;
+        target.mThemedBitmap = mThemedBitmap;
         target.flags = flags;
         target.badgeInfo = badgeInfo;
         return target;
@@ -102,9 +100,13 @@
         return copyInternalsTo(new BitmapInfo(icon, color));
     }
 
-    public void setMonoIcon(Bitmap mono, BaseIconFactory iconFactory) {
-        mMono = mono;
-        mWhiteShadowLayer = iconFactory.getWhiteShadowLayer();
+    public void setThemedBitmap(@Nullable ThemedBitmap themedBitmap) {
+        mThemedBitmap = themedBitmap;
+    }
+
+    @Nullable
+    public ThemedBitmap getThemedBitmap() {
+        return mThemedBitmap;
     }
 
     /**
@@ -125,10 +127,6 @@
         return !isNullOrLowRes();
     }
 
-    public Bitmap getMono() {
-        return mMono;
-    }
-
     /**
      * Creates a drawable for the provided BitmapInfo
      */
@@ -143,8 +141,8 @@
         FastBitmapDrawable drawable;
         if (isLowRes()) {
             drawable = new PlaceHolderIconDrawable(this, context);
-        } else  if ((creationFlags & FLAG_THEMED) != 0 && mMono != null) {
-            drawable = ThemedIconDrawable.newDrawable(this, context);
+        } else  if ((creationFlags & FLAG_THEMED) != 0 && mThemedBitmap != null) {
+            drawable = mThemedBitmap.newDrawable(this, context);
         } else {
             drawable = new FastBitmapDrawable(this);
         }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
index b625f3a..664294e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -22,7 +22,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
-import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.BlendMode;
 import android.graphics.BlendModeColorFilter;
@@ -39,11 +38,8 @@
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.util.Log;
-import android.util.TypedValue;
 
-import androidx.annotation.Nullable;
-
-import com.android.launcher3.icons.IconProvider.ThemeData;
+import com.android.launcher3.icons.mono.ThemedIconDrawable;
 
 import java.util.Calendar;
 import java.util.concurrent.TimeUnit;
@@ -53,7 +49,6 @@
  * Wrapper over {@link AdaptiveIconDrawable} to intercept icon flattening logic for dynamic
  * clock icons
  */
-@TargetApi(Build.VERSION_CODES.O)
 public class ClockDrawableWrapper extends AdaptiveIconDrawable implements BitmapInfo.Extender {
 
     public static boolean sRunningInTest = false;
@@ -94,34 +89,6 @@
         super(base.getBackground(), base.getForeground());
     }
 
-    private void applyThemeData(ThemeData themeData) {
-        if (!IconProvider.ATLEAST_T || mThemeInfo != null) {
-            return;
-        }
-        try {
-            TypedArray ta = themeData.mResources.obtainTypedArray(themeData.mResID);
-            int count = ta.length();
-            Bundle extras = new Bundle();
-            for (int i = 0; i < count; i += 2) {
-                TypedValue v = ta.peekValue(i + 1);
-                extras.putInt(ta.getString(i), v.type >= TypedValue.TYPE_FIRST_INT
-                        && v.type <= TypedValue.TYPE_LAST_INT
-                        ? v.data : v.resourceId);
-            }
-            ta.recycle();
-            ClockDrawableWrapper drawable = ClockDrawableWrapper.forExtras(extras, resId -> {
-                Drawable bg = new ColorDrawable(Color.WHITE);
-                Drawable fg = themeData.mResources.getDrawable(resId).mutate();
-                return new AdaptiveIconDrawable(bg, fg);
-            });
-            if (drawable != null) {
-                mThemeInfo = drawable.mAnimationInfo;
-            }
-        } catch (Exception e) {
-            Log.e(TAG, "Error loading themed clock", e);
-        }
-    }
-
     @Override
     public Drawable getMonochrome() {
         if (mThemeInfo == null) {
@@ -140,26 +107,19 @@
      * Loads and returns the wrapper from the provided package, or returns null
      * if it is unable to load.
      */
-    public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi,
-            @Nullable ThemeData themeData) {
+    public static ClockDrawableWrapper forPackage(Context context, String pkg, int iconDpi) {
         try {
             PackageManager pm = context.getPackageManager();
             ApplicationInfo appInfo =  pm.getApplicationInfo(pkg,
                     PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.GET_META_DATA);
             Resources res = pm.getResourcesForApplication(appInfo);
-            ClockDrawableWrapper wrapper = forExtras(appInfo.metaData,
-                    resId -> res.getDrawableForDensity(resId, iconDpi));
-            if (wrapper != null && themeData != null) {
-                wrapper.applyThemeData(themeData);
-            }
-            return wrapper;
+            return forExtras(appInfo.metaData, resId -> res.getDrawableForDensity(resId, iconDpi));
         } catch (Exception e) {
             Log.d(TAG, "Unable to load clock drawable info", e);
         }
         return null;
     }
 
-    @TargetApi(Build.VERSION_CODES.TIRAMISU)
     private static ClockDrawableWrapper forExtras(
             Bundle metadata, IntFunction<Drawable> drawableProvider) {
         if (metadata == null) {
@@ -220,7 +180,7 @@
                 BaseIconFactory.MODE_HARDWARE_WITH_SHADOW);
 
         // Only pass theme info if mono-icon is enabled
-        AnimationInfo themeInfo = iconFactory.mMonoIconEnabled ? mThemeInfo : null;
+        AnimationInfo themeInfo = iconFactory.getThemeController() != null ? mThemeInfo : null;
         Bitmap themeBG = themeInfo == null ? null : iconFactory.getWhiteShadowLayer();
         return new ClockBitmapInfo(bitmap, color, normalizationScale,
                 mAnimationInfo, flattenBG, themeInfo, themeBG);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java b/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
index 0e37ca9..50ca8d6 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/FastBitmapDrawable.java
@@ -73,7 +73,7 @@
     @VisibleForTesting protected boolean mIsPressed;
     @VisibleForTesting protected boolean mIsHovered;
     protected boolean mIsDisabled;
-    float mDisabledAlpha = 1f;
+    protected float mDisabledAlpha = 1f;
 
     @DrawableCreationFlags int mCreationFlags = 0;
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
index 15b5b21..594db35 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/IconProvider.java
@@ -151,7 +151,7 @@
         if (mCalendar != null && mCalendar.getPackageName().equals(packageName)) {
             icon = loadCalendarDrawable(iconDpi, td);
         } else if (mClock != null && mClock.getPackageName().equals(packageName)) {
-            icon = ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi, td);
+            icon = ClockDrawableWrapper.forPackage(mContext, mClock.getPackageName(), iconDpi);
         }
         if (icon == null) {
             icon = loadPackageIcon(info, appInfo, iconDpi);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/MonochromeIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/MonochromeIconFactory.java
index 2854d51..dc4ded8 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/MonochromeIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/MonochromeIconFactory.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * 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.
@@ -35,7 +35,7 @@
 
 import androidx.annotation.WorkerThread;
 
-import com.android.launcher3.icons.BaseIconFactory.ClippedMonoDrawable;
+import com.android.launcher3.icons.mono.MonoIconThemeController.ClippedMonoDrawable;
 
 import java.nio.ByteBuffer;
 
@@ -59,7 +59,7 @@
     private final Paint mDrawPaint;
     private final Rect mSrcRect;
 
-    MonochromeIconFactory(int iconBitmapSize) {
+    public MonochromeIconFactory(int iconBitmapSize) {
         float extraFactor = AdaptiveIconDrawable.getExtraInsetFraction();
         float viewPortScale = 1 / (1 + 2 * extraFactor);
         mBitmapSize = Math.round(iconBitmapSize * 2 * viewPortScale);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ThemedBitmap.kt b/iconloaderlib/src/com/android/launcher3/icons/ThemedBitmap.kt
new file mode 100644
index 0000000..27b4619
--- /dev/null
+++ b/iconloaderlib/src/com/android/launcher3/icons/ThemedBitmap.kt
@@ -0,0 +1,46 @@
+/*
+ * 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
+
+import android.content.Context
+import android.graphics.drawable.AdaptiveIconDrawable
+
+/** Represents a themed version of a BitmapInfo */
+interface ThemedBitmap {
+
+    /** Creates a new Drawable */
+    fun newDrawable(info: BitmapInfo, context: Context): FastBitmapDrawable
+
+    fun serialize(): ByteArray
+}
+
+interface IconThemeController {
+
+    fun createThemedBitmap(
+        icon: AdaptiveIconDrawable,
+        info: BitmapInfo,
+        factory: BaseIconFactory,
+    ): ThemedBitmap?
+
+    fun decode(data: ByteArray, info: BitmapInfo, factory: BaseIconFactory): ThemedBitmap?
+
+    fun createThemedAdaptiveIcon(
+        context: Context,
+        originalIcon: AdaptiveIconDrawable,
+        info: BitmapInfo?,
+    ): AdaptiveIconDrawable?
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index 4142a24..959f14d 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -59,13 +59,14 @@
 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
 import com.android.launcher3.icons.BitmapInfo;
 import com.android.launcher3.icons.IconProvider;
+import com.android.launcher3.icons.IconThemeController;
+import com.android.launcher3.icons.ThemedBitmap;
 import com.android.launcher3.util.ComponentKey;
 import com.android.launcher3.util.FlagOp;
 import com.android.launcher3.util.SQLiteCacheHelper;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.nio.ByteBuffer;
 import java.util.AbstractMap;
 import java.util.Arrays;
 import java.util.Collections;
@@ -692,20 +693,13 @@
                 return false;
             }
 
-            // Decode mono bitmap
-            data = c.getBlob(IconDB.INDEX_MONO_ICON);
-            Bitmap icon = entry.bitmap.icon;
-            if (data != null && data.length == icon.getHeight() * icon.getWidth()) {
-                Bitmap monoBitmap = Bitmap.createBitmap(
-                        icon.getWidth(), icon.getHeight(), Config.ALPHA_8);
-                monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data));
-                Bitmap hwMonoBitmap = monoBitmap.copy(Config.HARDWARE, false /*isMutable*/);
-                if (hwMonoBitmap != null) {
-                    monoBitmap.recycle();
-                    monoBitmap = hwMonoBitmap;
-                }
-                try (BaseIconFactory factory = getIconFactory()) {
-                    entry.bitmap.setMonoIcon(monoBitmap, factory);
+            // Decode theme bitmap
+            try (BaseIconFactory factory = getIconFactory()) {
+                IconThemeController themeController = factory.getThemeController();
+                data = c.getBlob(IconDB.INDEX_MONO_ICON);
+                if (themeController != null && data != null) {
+                    entry.bitmap.setThemedBitmap(
+                            themeController.decode(data, entry.bitmap, factory));
                 }
             }
         }
@@ -792,17 +786,9 @@
         if (bitmapInfo.canPersist()) {
             values.put(IconDB.COLUMN_ICON, flattenBitmap(bitmapInfo.icon));
 
-            // Persist mono bitmap as alpha channel
-            Bitmap mono = bitmapInfo.getMono();
-            if (mono != null && mono.getHeight() == bitmapInfo.icon.getHeight()
-                    && mono.getWidth() == bitmapInfo.icon.getWidth()
-                    && mono.getConfig() == Config.ALPHA_8) {
-                byte[] pixels = new byte[mono.getWidth() * mono.getHeight()];
-                mono.copyPixelsToBuffer(ByteBuffer.wrap(pixels));
-                values.put(IconDB.COLUMN_MONO_ICON, pixels);
-            } else {
-                values.put(IconDB.COLUMN_MONO_ICON, (byte[]) null);
-            }
+            ThemedBitmap themedBitmap = bitmapInfo.getThemedBitmap();
+            values.put(IconDB.COLUMN_MONO_ICON,
+                    themedBitmap != null ? themedBitmap.serialize() : null);
         } else {
             values.put(IconDB.COLUMN_ICON, (byte[]) null);
             values.put(IconDB.COLUMN_MONO_ICON, (byte[]) null);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/mono/MonoIconThemeController.kt b/iconloaderlib/src/com/android/launcher3/icons/mono/MonoIconThemeController.kt
new file mode 100644
index 0000000..cdaf05f
--- /dev/null
+++ b/iconloaderlib/src/com/android/launcher3/icons/mono/MonoIconThemeController.kt
@@ -0,0 +1,145 @@
+/*
+ * 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.annotation.TargetApi
+import android.content.Context
+import android.graphics.Bitmap
+import android.graphics.Bitmap.Config.ALPHA_8
+import android.graphics.Bitmap.Config.HARDWARE
+import android.graphics.BlendMode.SRC_IN
+import android.graphics.BlendModeColorFilter
+import android.graphics.Canvas
+import android.graphics.Color
+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 com.android.launcher3.Flags
+import com.android.launcher3.icons.BaseIconFactory
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.IconThemeController
+import com.android.launcher3.icons.MonochromeIconFactory
+import com.android.launcher3.icons.ThemedBitmap
+import com.android.launcher3.icons.mono.ThemedIconDrawable.Companion.getColors
+import java.nio.ByteBuffer
+
+@TargetApi(Build.VERSION_CODES.TIRAMISU)
+class MonoIconThemeController : IconThemeController {
+
+    override fun createThemedBitmap(
+        icon: AdaptiveIconDrawable,
+        info: BitmapInfo,
+        factory: BaseIconFactory,
+    ): ThemedBitmap? {
+        val mono = getMonochromeDrawable(icon, info)
+        if (mono != null) {
+            val scale =
+                factory.normalizer.getScale(
+                    AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null),
+                    null,
+                    null,
+                    null,
+                )
+            return MonoThemedBitmap(
+                factory.createIconBitmap(mono, scale, BaseIconFactory.MODE_ALPHA),
+                factory.whiteShadowLayer,
+            )
+        }
+        return null
+    }
+
+    /**
+     * Returns a monochromatic version of the given drawable or null, if it is not supported
+     *
+     * @param base the original icon
+     */
+    private fun getMonochromeDrawable(base: AdaptiveIconDrawable, info: BitmapInfo): Drawable? {
+        val mono = base.monochrome
+        if (mono != null) {
+            return ClippedMonoDrawable(mono)
+        }
+        if (Flags.forceMonochromeAppIcons()) {
+            return MonochromeIconFactory(info.icon.width).wrap(base)
+        }
+        return null
+    }
+
+    override fun decode(
+        data: ByteArray,
+        info: BitmapInfo,
+        factory: BaseIconFactory,
+    ): ThemedBitmap? {
+        val icon = info.icon
+        if (data.size != icon.height * icon.width) return null
+
+        var monoBitmap = Bitmap.createBitmap(icon.width, icon.height, ALPHA_8)
+        monoBitmap.copyPixelsFromBuffer(ByteBuffer.wrap(data))
+
+        val hwMonoBitmap = monoBitmap.copy(HARDWARE, false /*isMutable*/)
+        if (hwMonoBitmap != null) {
+            monoBitmap.recycle()
+            monoBitmap = hwMonoBitmap
+        }
+        return MonoThemedBitmap(monoBitmap, factory.whiteShadowLayer)
+    }
+
+    override fun createThemedAdaptiveIcon(
+        context: Context,
+        originalIcon: AdaptiveIconDrawable,
+        info: BitmapInfo?,
+    ): AdaptiveIconDrawable? {
+        val colors = getColors(context)
+        originalIcon.mutate()
+        var monoDrawable = originalIcon.monochrome?.apply { setTint(colors[1]) }
+
+        if (monoDrawable == null) {
+            info?.themedBitmap?.let { themedBitmap ->
+                if (themedBitmap is MonoThemedBitmap) {
+                    // Inject a previously generated monochrome icon
+                    // Use BitmapDrawable instead of FastBitmapDrawable so that the colorState is
+                    // preserved in constantState
+                    // Inset the drawable according to the AdaptiveIconDrawable layers
+                    monoDrawable =
+                        InsetDrawable(
+                            BitmapDrawable(themedBitmap.mono).apply {
+                                colorFilter = BlendModeColorFilter(colors[1], SRC_IN)
+                            },
+                            AdaptiveIconDrawable.getExtraInsetFraction() / 2,
+                        )
+                }
+            }
+        }
+
+        return monoDrawable?.let { AdaptiveIconDrawable(ColorDrawable(colors[0]), it) }
+    }
+
+    class ClippedMonoDrawable(base: Drawable?) :
+        InsetDrawable(base, -AdaptiveIconDrawable.getExtraInsetFraction()) {
+        private val mCrop = AdaptiveIconDrawable(ColorDrawable(Color.BLACK), null)
+
+        override fun draw(canvas: Canvas) {
+            mCrop.bounds = bounds
+            val saveCount = canvas.save()
+            canvas.clipPath(mCrop.iconMask)
+            super.draw(canvas)
+            canvas.restoreToCount(saveCount)
+        }
+    }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/mono/MonoThemedBitmap.kt b/iconloaderlib/src/com/android/launcher3/icons/mono/MonoThemedBitmap.kt
new file mode 100644
index 0000000..dc6030e
--- /dev/null
+++ b/iconloaderlib/src/com/android/launcher3/icons/mono/MonoThemedBitmap.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.content.Context
+import android.graphics.Bitmap
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.ThemedBitmap
+import com.android.launcher3.icons.mono.ThemedIconDrawable.ThemedConstantState
+import java.nio.ByteBuffer
+
+class MonoThemedBitmap(val mono: Bitmap, private val whiteShadowLayer: Bitmap) : ThemedBitmap {
+
+    override fun newDrawable(info: BitmapInfo, context: Context): FastBitmapDrawable {
+        val colors = ThemedIconDrawable.getColors(context)
+        return ThemedConstantState(info, mono, whiteShadowLayer, colors[0], colors[1]).newDrawable()
+    }
+
+    override fun serialize() =
+        ByteArray(mono.width * mono.height).apply { mono.copyPixelsToBuffer(ByteBuffer.wrap(this)) }
+}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.kt b/iconloaderlib/src/com/android/launcher3/icons/mono/ThemedIconDrawable.kt
similarity index 78%
rename from iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.kt
rename to iconloaderlib/src/com/android/launcher3/icons/mono/ThemedIconDrawable.kt
index 721b281..59fb245 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ThemedIconDrawable.kt
+++ b/iconloaderlib/src/com/android/launcher3/icons/mono/ThemedIconDrawable.kt
@@ -13,29 +13,33 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.launcher3.icons
+package com.android.launcher3.icons.mono
 
 import android.content.Context
+import android.graphics.Bitmap
 import android.graphics.BlendMode.SRC_IN
 import android.graphics.BlendModeColorFilter
 import android.graphics.Canvas
 import android.graphics.Paint
 import android.graphics.Rect
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.FastBitmapDrawable
+import com.android.launcher3.icons.R
 
 /** Class to handle monochrome themed app icons */
 class ThemedIconDrawable(constantState: ThemedConstantState) :
-    FastBitmapDrawable(constantState.mBitmap, constantState.colorFg) {
+    FastBitmapDrawable(constantState.getBitmap(), constantState.colorFg) {
     val bitmapInfo = constantState.bitmapInfo
     private val colorFg = constantState.colorFg
     private val colorBg = constantState.colorBg
 
     // The foreground/monochrome icon for the app
-    private val monoIcon = bitmapInfo.mMono!!
+    private val monoIcon = constantState.mono
     private val monoFilter = BlendModeColorFilter(colorFg, SRC_IN)
     private val monoPaint =
         Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { colorFilter = monoFilter }
 
-    private val bgBitmap = bitmapInfo.mWhiteShadowLayer
+    private val bgBitmap = constantState.whiteShadowLayer
     private val bgFilter = BlendModeColorFilter(colorBg, SRC_IN)
     private val mBgPaint =
         Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG).apply { colorFilter = bgFilter }
@@ -61,25 +65,27 @@
 
     override fun isThemed() = true
 
-    override fun newConstantState() = ThemedConstantState(bitmapInfo, colorBg, colorFg)
+    override fun newConstantState() =
+        ThemedConstantState(bitmapInfo, monoIcon, bgBitmap, colorBg, colorFg)
 
     override fun getIconColor() = colorFg
 
-    class ThemedConstantState(val bitmapInfo: BitmapInfo, val colorBg: Int, val colorFg: Int) :
-        FastBitmapConstantState(bitmapInfo.icon, bitmapInfo.color) {
+    class ThemedConstantState(
+        val bitmapInfo: BitmapInfo,
+        val mono: Bitmap,
+        val whiteShadowLayer: Bitmap,
+        val colorBg: Int,
+        val colorFg: Int,
+    ) : FastBitmapConstantState(bitmapInfo.icon, bitmapInfo.color) {
 
         public override fun createDrawable() = ThemedIconDrawable(this)
+
+        fun getBitmap(): Bitmap = mBitmap
     }
 
     companion object {
         const val TAG: String = "ThemedIconDrawable"
 
-        @JvmStatic
-        fun newDrawable(info: BitmapInfo, context: Context): FastBitmapDrawable {
-            val colors = getColors(context)
-            return ThemedConstantState(info, colors[0], colors[1]).newDrawable()
-        }
-
         /** Get an int array representing background and foreground colors for themed icons */
         @JvmStatic
         fun getColors(context: Context): IntArray {
diff --git a/toruslib/torus-core/src/main/java/com/google/android/torus/core/wallpaper/LiveWallpaper.kt b/toruslib/torus-core/src/main/java/com/google/android/torus/core/wallpaper/LiveWallpaper.kt
index 6c072d0..533a95e 100644
--- a/toruslib/torus-core/src/main/java/com/google/android/torus/core/wallpaper/LiveWallpaper.kt
+++ b/toruslib/torus-core/src/main/java/com/google/android/torus/core/wallpaper/LiveWallpaper.kt
@@ -197,6 +197,16 @@
             return false
         }
 
+        /**
+         * Returns the information if the wallpaper is visible.
+         */
+        fun isVisible(): Boolean {
+            this.wallpaperServiceEngine?.let {
+                return it.isVisible
+            }
+            return false
+        }
+
         /** Triggers the [WallpaperService] to recompute the Wallpaper Colors. */
         fun notifyWallpaperColorsChanged() {
             this.wallpaperServiceEngine?.notifyColorsChanged()
diff --git a/viewcapturelib/OWNERS b/viewcapturelib/OWNERS
index 30bdc84..2f30b7c 100644
--- a/viewcapturelib/OWNERS
+++ b/viewcapturelib/OWNERS
@@ -1,2 +1,3 @@
 sunnygoyal@google.com
 andonian@google.com
+include platform/development:/tools/winscope/OWNERS
diff --git a/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java b/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
index 761b863..33f6a95 100644
--- a/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
+++ b/viewcapturelib/src/com/android/app/viewcapture/ViewCapture.java
@@ -311,8 +311,12 @@
             captureViewTree(mRoot, mViewPropertyRef);
             ViewPropertyRef captured = mViewPropertyRef.next;
             if (captured != null) {
-                captured.callback = mCaptureCallback;
                 captured.elapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
+
+                // Main thread writes volatile field:
+                // guarantee that variable changes prior the field write are visible to bg thread
+                captured.volatileCallback = mCaptureCallback;
+
                 mBgExecutor.execute(captured);
             }
             mIsFirstFrame = false;
@@ -552,9 +556,11 @@
 
         public ViewPropertyRef next;
 
-        public Consumer<ViewPropertyRef> callback = null;
         public long elapsedRealtimeNanos = 0;
 
+        // Volatile field to establish happens-before relationship between main and bg threads
+        // (see JSR-133: Java Memory Model and Thread Specification)
+        public volatile Consumer<ViewPropertyRef> volatileCallback = null;
 
         public void transferFrom(View in) {
             view = in;
@@ -651,8 +657,10 @@
 
         @Override
         public void run() {
-            Consumer<ViewPropertyRef> oldCallback = callback;
-            callback = null;
+            // Bg thread reads volatile field:
+            // guarantee that variable changes in main thread prior the field write are visible
+            Consumer<ViewPropertyRef> oldCallback = volatileCallback;
+            volatileCallback = null;
             if (oldCallback != null) {
                 oldCallback.accept(this);
             }