Snap for 9239618 from 34bc3c48b081547bcb9409879bb466ef9c4e2690 to tm-platform-release

Change-Id: I864d8c994efb932d51ad56f2cb29467de562569a
diff --git a/iconloaderlib/build.gradle b/iconloaderlib/build.gradle
index 10ec889..23c7cb0 100644
--- a/iconloaderlib/build.gradle
+++ b/iconloaderlib/build.gradle
@@ -1,14 +1,8 @@
-apply plugin: 'com.android.library'
+plugins {
+    id 'sysuigradleproject.android-library-conventions'
+}
 
 android {
-    compileSdkVersion COMPILE_SDK
-    buildToolsVersion BUILD_TOOLS_VERSION
-
-    defaultConfig {
-        minSdkVersion 26
-        targetSdkVersion 28
-    }
-
     sourceSets {
         main {
             java.srcDirs = ['src', 'src_full_lib']
@@ -24,13 +18,8 @@
     tasks.withType(JavaCompile) {
         options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation"
     }
-
-    compileOptions {
-        sourceCompatibility JavaVersion.VERSION_1_8
-        targetCompatibility JavaVersion.VERSION_1_8
-    }
 }
 
 dependencies {
-    implementation "androidx.core:core:${ANDROID_X_VERSION}"
+    implementation "androidx.core:core"
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
index c0be55d..836d3a8 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BaseIconFactory.java
@@ -1,5 +1,6 @@
 package com.android.launcher3.icons;
 
+import static android.graphics.Paint.ANTI_ALIAS_FLAG;
 import static android.graphics.Paint.DITHER_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 import static android.graphics.drawable.AdaptiveIconDrawable.getExtraInsetFraction;
@@ -36,6 +37,8 @@
 import com.android.launcher3.icons.BitmapInfo.Extender;
 import com.android.launcher3.util.FlagOp;
 
+import java.util.Objects;
+
 /**
  * This class will be moved to androidx library. There shouldn't be any dependency outside
  * this package.
@@ -44,14 +47,30 @@
 
     private static final int DEFAULT_WRAPPER_BACKGROUND = Color.WHITE;
 
+    protected static final int BITMAP_GENERATION_MODE_DEFAULT = 0;
+    protected static final int BITMAP_GENERATION_MODE_ALPHA = 1;
+    protected static final int BITMAP_GENERATION_MODE_WITH_SHADOW = 2;
+
     private static final float ICON_BADGE_SCALE = 0.444f;
 
+    @NonNull
     private final Rect mOldBounds = new Rect();
+
+    @NonNull
     private final SparseBooleanArray mIsUserBadged = new SparseBooleanArray();
+
+    @NonNull
     protected final Context mContext;
+
+    @NonNull
     private final Canvas mCanvas;
+
+    @NonNull
     private final PackageManager mPm;
+
+    @NonNull
     private final ColorExtractor mColorExtractor;
+
     private boolean mDisableColorExtractor;
 
     protected final int mFillResIconDpi;
@@ -59,8 +78,12 @@
 
     protected boolean mMonoIconEnabled;
 
+    @Nullable
     private IconNormalizer mNormalizer;
+
+    @Nullable
     private ShadowGenerator mShadowGenerator;
+
     private final boolean mShapeDetection;
 
     // Shadow bitmap used as background for theme icons
@@ -69,8 +92,6 @@
     private Drawable mWrapperIcon;
     private int mWrapperBackgroundColor = DEFAULT_WRAPPER_BACKGROUND;
 
-    private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
-    private static final float PLACEHOLDER_TEXT_SIZE = 20f;
     private static int PLACEHOLDER_BACKGROUND_COLOR = Color.rgb(245, 245, 245);
 
     protected BaseIconFactory(Context context, int fillResIconDpi, int iconBitmapSize,
@@ -85,10 +106,6 @@
 
         mCanvas = new Canvas();
         mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
-        mTextPaint.setTextAlign(Paint.Align.CENTER);
-        mTextPaint.setColor(PLACEHOLDER_BACKGROUND_COLOR);
-        mTextPaint.setTextSize(context.getResources().getDisplayMetrics().density *
-                PLACEHOLDER_TEXT_SIZE);
         clear();
     }
 
@@ -101,6 +118,7 @@
         mDisableColorExtractor = false;
     }
 
+    @NonNull
     public ShadowGenerator getShadowGenerator() {
         if (mShadowGenerator == null) {
             mShadowGenerator = new ShadowGenerator(mIconBitmapSize);
@@ -108,6 +126,7 @@
         return mShadowGenerator;
     }
 
+    @NonNull
     public IconNormalizer getNormalizer() {
         if (mNormalizer == null) {
             mNormalizer = new IconNormalizer(mContext, mIconBitmapSize, mShapeDetection);
@@ -138,16 +157,11 @@
      * @return
      */
     public BitmapInfo createIconBitmap(String placeholder, int color) {
-        Bitmap placeholderBitmap = Bitmap.createBitmap(mIconBitmapSize, mIconBitmapSize,
-                Bitmap.Config.ARGB_8888);
-        mTextPaint.setColor(color);
-        Canvas canvas = new Canvas(placeholderBitmap);
-        canvas.drawText(placeholder, mIconBitmapSize / 2, mIconBitmapSize * 5 / 8, mTextPaint);
         AdaptiveIconDrawable drawable = new AdaptiveIconDrawable(
                 new ColorDrawable(PLACEHOLDER_BACKGROUND_COLOR),
-                new BitmapDrawable(mContext.getResources(), placeholderBitmap));
+                new CenterTextDrawable(placeholder, color));
         Bitmap icon = createIconBitmap(drawable, IconNormalizer.ICON_VISIBLE_AREA_FACTOR);
-        return BitmapInfo.of(icon, extractColor(icon));
+        return BitmapInfo.of(icon, color);
     }
 
     public BitmapInfo createIconBitmap(Bitmap icon) {
@@ -161,6 +175,7 @@
     /**
      * Creates an icon from the bitmap cropped to the current device icon shape
      */
+    @NonNull
     public BitmapInfo createShapedIconBitmap(Bitmap icon, IconOptions options) {
         Drawable d = new FixedSizeBitmapDrawable(icon);
         float inset = getExtraInsetFraction();
@@ -170,6 +185,7 @@
         return createBadgedIconBitmap(d, options);
     }
 
+    @NonNull
     public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon) {
         return createBadgedIconBitmap(icon, null);
     }
@@ -182,17 +198,13 @@
      * @return a bitmap suitable for disaplaying as an icon at various system UIs.
      */
     @TargetApi(Build.VERSION_CODES.TIRAMISU)
+    @NonNull
     public BitmapInfo createBadgedIconBitmap(@NonNull Drawable icon,
             @Nullable IconOptions options) {
         boolean shrinkNonAdaptiveIcons = options == null || options.mShrinkNonAdaptiveIcons;
         float[] scale = new float[1];
         icon = normalizeAndWrapToAdaptiveIcon(icon, shrinkNonAdaptiveIcons, null, scale);
-        Bitmap bitmap = createIconBitmap(icon, scale[0]);
-        if (icon instanceof AdaptiveIconDrawable) {
-            mCanvas.setBitmap(bitmap);
-            getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
-            mCanvas.setBitmap(null);
-        }
+        Bitmap bitmap = createIconBitmap(icon, scale[0], BITMAP_GENERATION_MODE_WITH_SHADOW);
 
         int color = extractColor(bitmap);
         BitmapInfo info = BitmapInfo.of(bitmap, color);
@@ -206,14 +218,14 @@
                 // Convert mono drawable to bitmap
                 Drawable paddedMono = new ClippedMonoDrawable(mono);
                 info.setMonoIcon(
-                        createIconBitmap(paddedMono, scale[0], mIconBitmapSize, Config.ALPHA_8),
-                        this);
+                        createIconBitmap(paddedMono, scale[0], BITMAP_GENERATION_MODE_ALPHA), this);
             }
         }
         info = info.withFlags(getBitmapFlagOp(options));
         return info;
     }
 
+    @NonNull
     public FlagOp getBitmapFlagOp(@Nullable IconOptions options) {
         FlagOp op = FlagOp.NO_OP;
         if (options != null) {
@@ -240,6 +252,7 @@
     }
 
     /** package private */
+    @NonNull
     Bitmap getWhiteShadowLayer() {
         if (mWhiteShadowLayer == null) {
             mWhiteShadowLayer = createScaledBitmapWithShadow(
@@ -248,17 +261,16 @@
         return mWhiteShadowLayer;
     }
 
-    /** package private */
-    public Bitmap createScaledBitmapWithShadow(Drawable d) {
+    @NonNull
+    public Bitmap createScaledBitmapWithShadow(@NonNull final Drawable d) {
         float scale = getNormalizer().getScale(d, null, null, null);
         Bitmap bitmap = createIconBitmap(d, scale);
-        mCanvas.setBitmap(bitmap);
-        getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
-        mCanvas.setBitmap(null);
-        return bitmap;
+        return BitmapRenderer.createHardwareBitmap(bitmap.getWidth(), bitmap.getHeight(),
+                canvas -> getShadowGenerator().recreateIcon(bitmap, canvas));
     }
 
-    public Bitmap createScaledBitmapWithoutShadow(Drawable icon) {
+    @NonNull
+    public Bitmap createScaledBitmapWithoutShadow(@Nullable Drawable icon) {
         RectF iconBounds = new RectF();
         float[] scale = new float[1];
         icon = normalizeAndWrapToAdaptiveIcon(icon, true, iconBounds, scale);
@@ -269,7 +281,7 @@
     /**
      * Sets the background color used for wrapped adaptive icon
      */
-    public void setWrapperBackgroundColor(int color) {
+    public void setWrapperBackgroundColor(final int color) {
         mWrapperBackgroundColor = (Color.alpha(color) < 255) ? DEFAULT_WRAPPER_BACKGROUND : color;
     }
 
@@ -280,8 +292,10 @@
         mDisableColorExtractor = true;
     }
 
-    private Drawable normalizeAndWrapToAdaptiveIcon(@NonNull Drawable icon,
-            boolean shrinkNonAdaptiveIcons, RectF outIconBounds, float[] outScale) {
+    @Nullable
+    protected Drawable normalizeAndWrapToAdaptiveIcon(@Nullable Drawable icon,
+            final boolean shrinkNonAdaptiveIcons, @Nullable final RectF outIconBounds,
+            @NonNull final float[] outScale) {
         if (icon == null) {
             return null;
         }
@@ -312,21 +326,26 @@
         return icon;
     }
 
-    private Bitmap createIconBitmap(Drawable icon, float scale) {
-        return createIconBitmap(icon, scale, mIconBitmapSize);
+    @NonNull
+    protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale) {
+        return createIconBitmap(icon, scale, BITMAP_GENERATION_MODE_DEFAULT);
     }
 
-    /**
-     * @param icon drawable that should be flattened to a bitmap
-     * @param scale the scale to apply before drawing {@param icon} on the canvas
-     */
-    public Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size) {
-        return createIconBitmap(icon, scale, size, Bitmap.Config.ARGB_8888);
-    }
+    @NonNull
+    protected Bitmap createIconBitmap(@Nullable final Drawable icon, final float scale,
+            final int bitmapGenerationMode) {
+        final int size = mIconBitmapSize;
 
-    private Bitmap createIconBitmap(@NonNull Drawable icon, float scale, int size,
-            Bitmap.Config config) {
-        Bitmap bitmap = Bitmap.createBitmap(size, size, config);
+        final Bitmap bitmap;
+        switch (bitmapGenerationMode) {
+            case BITMAP_GENERATION_MODE_ALPHA:
+                bitmap = Bitmap.createBitmap(size, size, Config.ALPHA_8);
+                break;
+            case BITMAP_GENERATION_MODE_WITH_SHADOW:
+            default:
+                bitmap = Bitmap.createBitmap(size, size, Config.ARGB_8888);
+                break;
+        }
         if (icon == null) {
             return bitmap;
         }
@@ -335,11 +354,15 @@
 
         if (icon instanceof AdaptiveIconDrawable) {
             int offset = Math.max((int) Math.ceil(BLUR_FACTOR * size),
-                    Math.round(size * (1 - scale) / 2 ));
+                    Math.round(size * (1 - scale) / 2));
             // b/211896569: AdaptiveIconDrawable do not work properly for non top-left bounds
             icon.setBounds(0, 0, size - offset - offset, size - offset - offset);
             int count = mCanvas.save();
             mCanvas.translate(offset, offset);
+            if (bitmapGenerationMode == BITMAP_GENERATION_MODE_WITH_SHADOW) {
+                getShadowGenerator().addPathShadow(
+                        ((AdaptiveIconDrawable) icon).getIconMask(), mCanvas);
+            }
 
             if (icon instanceof BitmapInfo.Extender) {
                 ((Extender) icon).drawForPersistence(mCanvas);
@@ -351,7 +374,7 @@
             if (icon instanceof BitmapDrawable) {
                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                 Bitmap b = bitmapDrawable.getBitmap();
-                if (bitmap != null && b.getDensity() == Bitmap.DENSITY_NONE) {
+                if (b != null && b.getDensity() == Bitmap.DENSITY_NONE) {
                     bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
                 }
             }
@@ -388,36 +411,42 @@
         clear();
     }
 
+    @NonNull
     public BitmapInfo makeDefaultIcon() {
         return createBadgedIconBitmap(getFullResDefaultActivityIcon(mFillResIconDpi));
     }
 
-    public static Drawable getFullResDefaultActivityIcon(int iconDpi) {
-        return Resources.getSystem().getDrawableForDensity(
-                android.R.drawable.sym_def_app_icon, iconDpi);
+    @NonNull
+    public static Drawable getFullResDefaultActivityIcon(final int iconDpi) {
+        return Objects.requireNonNull(Resources.getSystem().getDrawableForDensity(
+                android.R.drawable.sym_def_app_icon, iconDpi));
     }
 
-    private int extractColor(Bitmap bitmap) {
+    private int extractColor(@NonNull final Bitmap bitmap) {
         return mDisableColorExtractor ? 0 : mColorExtractor.findDominantColorByHue(bitmap);
     }
 
     /**
      * Returns the correct badge size given an icon size
      */
-    public static int getBadgeSizeForIconSize(int iconSize) {
+    public static int getBadgeSizeForIconSize(final int iconSize) {
         return (int) (ICON_BADGE_SCALE * iconSize);
     }
 
     public static class IconOptions {
 
         boolean mShrinkNonAdaptiveIcons = true;
+
         boolean mIsInstantApp;
+
+        @Nullable
         UserHandle mUserHandle;
 
         /**
          * Set to false if non-adaptive icons should not be treated
          */
-        public IconOptions setShrinkNonAdaptiveIcons(boolean shrink) {
+        @NonNull
+        public IconOptions setShrinkNonAdaptiveIcons(final boolean shrink) {
             mShrinkNonAdaptiveIcons = shrink;
             return this;
         }
@@ -425,7 +454,8 @@
         /**
          * User for this icon, in case of badging
          */
-        public IconOptions setUser(UserHandle user) {
+        @NonNull
+        public IconOptions setUser(@Nullable final UserHandle user) {
             mUserHandle = user;
             return this;
         }
@@ -433,7 +463,8 @@
         /**
          * If this icon represents an instant app
          */
-        public IconOptions setInstantApp(boolean instantApp) {
+        @NonNull
+        public IconOptions setInstantApp(final boolean instantApp) {
             mIsInstantApp = instantApp;
             return this;
         }
@@ -446,7 +477,7 @@
      */
     private static class FixedSizeBitmapDrawable extends BitmapDrawable {
 
-        public FixedSizeBitmapDrawable(Bitmap bitmap) {
+        public FixedSizeBitmapDrawable(@Nullable final Bitmap bitmap) {
             super(null, bitmap);
         }
 
@@ -475,9 +506,10 @@
 
     private static class ClippedMonoDrawable extends InsetDrawable {
 
+        @NonNull
         private final AdaptiveIconDrawable mCrop;
 
-        public ClippedMonoDrawable(Drawable base) {
+        public ClippedMonoDrawable(@Nullable final Drawable base) {
             super(base, -getExtraInsetFraction());
             mCrop = new AdaptiveIconDrawable(new ColorDrawable(Color.BLACK), null);
         }
@@ -491,4 +523,32 @@
             canvas.restoreToCount(saveCount);
         }
     }
+
+    private static class CenterTextDrawable extends ColorDrawable {
+
+        @NonNull
+        private final Rect mTextBounds = new Rect();
+
+        @NonNull
+        private final Paint mTextPaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG);
+
+        @NonNull
+        private final String mText;
+
+        CenterTextDrawable(@NonNull final String text, final int color) {
+            mText = text;
+            mTextPaint.setColor(color);
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            Rect bounds = getBounds();
+            mTextPaint.setTextSize(bounds.height() / 3f);
+            mTextPaint.getTextBounds(mText, 0, mText.length(), mTextBounds);
+            canvas.drawText(mText,
+                    bounds.exactCenterX() - mTextBounds.exactCenterX(),
+                    bounds.exactCenterY() - mTextBounds.exactCenterY(),
+                    mTextPaint);
+        }
+    }
 }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
index d624805..167fca4 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ClockDrawableWrapper.java
@@ -387,7 +387,8 @@
             mBgPaint.setColorFilter(cs.mBgFilter);
             mThemedFgColor = cs.mThemedFgColor;
 
-            mFullDrawable = (AdaptiveIconDrawable) mAnimInfo.baseDrawableState.newDrawable();
+            mFullDrawable =
+                    (AdaptiveIconDrawable) mAnimInfo.baseDrawableState.newDrawable().mutate();
             mFG = (LayerDrawable) mFullDrawable.getForeground();
 
             // Time needs to be applied here since drawInternal is NOT guaranteed to be called
@@ -397,6 +398,13 @@
         }
 
         @Override
+        public void setAlpha(int alpha) {
+            super.setAlpha(alpha);
+            mBgPaint.setAlpha(alpha);
+            mFG.setAlpha(alpha);
+        }
+
+        @Override
         protected void onBoundsChange(Rect bounds) {
             super.onBoundsChange(bounds);
 
@@ -434,8 +442,7 @@
         protected void updateFilter() {
             super.updateFilter();
             int alpha = mIsDisabled ? (int) (mDisabledAlpha * FULLY_OPAQUE) : FULLY_OPAQUE;
-            mBgPaint.setAlpha(alpha);
-            mFG.setAlpha(alpha);
+            setAlpha(alpha);
             mBgPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : mBgFilter);
             mFG.setColorFilter(mIsDisabled ? getDisabledColorFilter() : null);
         }
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java b/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
index 87bda82..56bb3ea 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ColorExtractor.java
@@ -18,6 +18,9 @@
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.util.SparseArray;
+
+import androidx.annotation.NonNull;
+
 import java.util.Arrays;
 
 /**
@@ -26,16 +29,24 @@
 public class ColorExtractor {
 
     private final int NUM_SAMPLES = 20;
+
+    @NonNull
     private final float[] mTmpHsv = new float[3];
+
+    @NonNull
     private final float[] mTmpHueScoreHistogram = new float[360];
+
+    @NonNull
     private final int[] mTmpPixels = new int[NUM_SAMPLES];
+
+    @NonNull
     private final SparseArray<Float> mTmpRgbScores = new SparseArray<>();
 
     /**
      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
      * @param bitmap The bitmap to scan
      */
-    public int findDominantColorByHue(Bitmap bitmap) {
+    public int findDominantColorByHue(@NonNull final Bitmap bitmap) {
         return findDominantColorByHue(bitmap, NUM_SAMPLES);
     }
 
@@ -43,7 +54,7 @@
      * This picks a dominant color, looking for high-saturation, high-value, repeated hues.
      * @param bitmap The bitmap to scan
      */
-    public int findDominantColorByHue(Bitmap bitmap, int samples) {
+    public int findDominantColorByHue(@NonNull final Bitmap bitmap, final int samples) {
         final int height = bitmap.getHeight();
         final int width = bitmap.getWidth();
         int sampleStride = (int) Math.sqrt((height * width) / samples);
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
index 96dee3b..be49ffd 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -24,6 +24,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
 import android.graphics.RectF;
@@ -58,22 +59,17 @@
     }
 
     public synchronized void recreateIcon(Bitmap icon, Canvas out) {
-        recreateIcon(icon, mDefaultBlurMaskFilter, AMBIENT_SHADOW_ALPHA, KEY_SHADOW_ALPHA, out);
-    }
-
-    public synchronized void recreateIcon(Bitmap icon, BlurMaskFilter blurMaskFilter,
-            int ambientAlpha, int keyAlpha, Canvas out) {
         if (ENABLE_SHADOWS) {
             int[] offset = new int[2];
-            mBlurPaint.setMaskFilter(blurMaskFilter);
+            mBlurPaint.setMaskFilter(mDefaultBlurMaskFilter);
             Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
 
             // Draw ambient shadow
-            mDrawPaint.setAlpha(ambientAlpha);
+            mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
             out.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);
 
             // Draw key shadow
-            mDrawPaint.setAlpha(keyAlpha);
+            mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
             out.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize,
                     mDrawPaint);
         }
@@ -83,6 +79,26 @@
         out.drawBitmap(icon, 0, 0, mDrawPaint);
     }
 
+    /** package private **/
+    void addPathShadow(Path path, Canvas out) {
+        if (ENABLE_SHADOWS) {
+            mDrawPaint.setMaskFilter(mDefaultBlurMaskFilter);
+
+            // Draw ambient shadow
+            mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
+            out.drawPath(path, mDrawPaint);
+
+            // Draw key shadow
+            int save = out.save();
+            mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
+            out.translate(0, KEY_SHADOW_DISTANCE * mIconSize);
+            out.drawPath(path, mDrawPaint);
+            out.restoreToCount(save);
+
+            mDrawPaint.setMaskFilter(null);
+        }
+    }
+
     /**
      * Returns the minimum amount by which an icon with {@param bounds} should be scaled
      * so that the shadows do not get clipped.
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
index 057bdc2..963e807 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/BaseIconCache.java
@@ -54,6 +54,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
+import androidx.annotation.WorkerThread;
 
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.BaseIconFactory.IconOptions;
@@ -86,29 +87,50 @@
 
         @NonNull
         public BitmapInfo bitmap = BitmapInfo.LOW_RES_INFO;
+        @NonNull
         public CharSequence title = "";
+        @NonNull
         public CharSequence contentDescription = "";
     }
 
+    @NonNull
     protected final Context mContext;
+
+    @NonNull
     protected final PackageManager mPackageManager;
 
+    @NonNull
     private final Map<ComponentKey, CacheEntry> mCache;
+
+    @NonNull
     protected final Handler mWorkerHandler;
 
     protected int mIconDpi;
+
+    @NonNull
     protected IconDB mIconDb;
+
+    @NonNull
     protected LocaleList mLocaleList = LocaleList.getEmptyLocaleList();
+
+    @NonNull
     protected String mSystemState = "";
 
+    @Nullable
     private BitmapInfo mDefaultIcon;
+
+    @NonNull
     private final SparseArray<FlagOp> mUserFlagOpMap = new SparseArray<>();
 
+    @Nullable
     private final String mDbFileName;
+
+    @NonNull
     private final Looper mBgLooper;
 
-    public BaseIconCache(Context context, String dbFileName, Looper bgLooper,
-            int iconDpi, int iconPixelSize, boolean inMemoryCache) {
+    public BaseIconCache(@NonNull final Context context, @Nullable final String dbFileName,
+            @NonNull final Looper bgLooper, final int iconDpi, final int iconPixelSize,
+            final boolean inMemoryCache) {
         mContext = context;
         mDbFileName = dbFileName;
         mPackageManager = context.getPackageManager();
@@ -141,23 +163,24 @@
      * Returns the persistable serial number for {@param user}. Subclass should implement proper
      * caching strategy to avoid making binder call every time.
      */
-    protected abstract long getSerialNumberForUser(UserHandle user);
+    protected abstract long getSerialNumberForUser(@NonNull final UserHandle user);
 
     /**
      * Return true if the given app is an instant app and should be badged appropriately.
      */
-    protected abstract boolean isInstantApp(ApplicationInfo info);
+    protected abstract boolean isInstantApp(@NonNull final ApplicationInfo info);
 
     /**
      * Opens and returns an icon factory. The factory is recycled by the caller.
      */
+    @NonNull
     public abstract BaseIconFactory getIconFactory();
 
-    public void updateIconParams(int iconDpi, int iconPixelSize) {
+    public void updateIconParams(final int iconDpi, final int iconPixelSize) {
         mWorkerHandler.post(() -> updateIconParamsBg(iconDpi, iconPixelSize));
     }
 
-    private synchronized void updateIconParamsBg(int iconDpi, int iconPixelSize) {
+    private synchronized void updateIconParamsBg(final int iconDpi, final int iconPixelSize) {
         mIconDpi = iconDpi;
         mDefaultIcon = null;
         mUserFlagOpMap.clear();
@@ -167,7 +190,8 @@
         mCache.clear();
     }
 
-    private Drawable getFullResIcon(Resources resources, int iconId) {
+    @Nullable
+    private Drawable getFullResIcon(@Nullable final Resources resources, final int iconId) {
         if (resources != null && iconId != 0) {
             try {
                 return resources.getDrawableForDensity(iconId, mIconDpi);
@@ -176,14 +200,16 @@
         return getFullResDefaultActivityIcon(mIconDpi);
     }
 
-    public Drawable getFullResIcon(String packageName, int iconId) {
+    @Nullable
+    public Drawable getFullResIcon(@NonNull final String packageName, final int iconId) {
         try {
             return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId);
         } catch (PackageManager.NameNotFoundException e) { }
         return getFullResDefaultActivityIcon(mIconDpi);
     }
 
-    public Drawable getFullResIcon(ActivityInfo info) {
+    @Nullable
+    public Drawable getFullResIcon(@NonNull final ActivityInfo info) {
         try {
             return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo),
                     info.getIconResource());
@@ -194,14 +220,16 @@
     /**
      * Remove any records for the supplied ComponentName.
      */
-    public synchronized void remove(ComponentName componentName, UserHandle user) {
+    public synchronized void remove(@NonNull final ComponentName componentName,
+            @NonNull final UserHandle user) {
         mCache.remove(new ComponentKey(componentName, user));
     }
 
     /**
      * Remove any records for the supplied package name from memory.
      */
-    private void removeFromMemCacheLocked(String packageName, UserHandle user) {
+    private void removeFromMemCacheLocked(@Nullable final String packageName,
+            @Nullable final UserHandle user) {
         HashSet<ComponentKey> forDeletion = new HashSet<>();
         for (ComponentKey key: mCache.keySet()) {
             if (key.componentName.getPackageName().equals(packageName)
@@ -217,7 +245,8 @@
     /**
      * Removes the entries related to the given package in memory and persistent DB.
      */
-    public synchronized void removeIconsForPkg(String packageName, UserHandle user) {
+    public synchronized void removeIconsForPkg(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         removeFromMemCacheLocked(packageName, user);
         long userSerial = getSerialNumberForUser(user);
         mIconDb.delete(
@@ -225,6 +254,7 @@
                 new String[]{packageName + "/%", Long.toString(userSerial)});
     }
 
+    @NonNull
     public IconCacheUpdateHandler getUpdateHandler() {
         updateSystemState();
         return new IconCacheUpdateHandler(this);
@@ -240,7 +270,8 @@
         mSystemState = mLocaleList.toLanguageTags() + "," + Build.VERSION.SDK_INT;
     }
 
-    protected String getIconSystemState(String packageName) {
+    @NonNull
+    protected String getIconSystemState(@Nullable final String packageName) {
         return mSystemState;
     }
 
@@ -251,8 +282,9 @@
      *                        old data.
      */
     @VisibleForTesting
-    public synchronized <T> void addIconToDBAndMemCache(T object, CachingLogic<T> cachingLogic,
-            PackageInfo info, long userSerial, boolean replaceExisting) {
+    public synchronized <T> void addIconToDBAndMemCache(@NonNull final T object,
+            @NonNull final CachingLogic<T> cachingLogic, @NonNull final PackageInfo info,
+            final long userSerial, final boolean replaceExisting) {
         UserHandle user = cachingLogic.getUser(object);
         ComponentName componentName = cachingLogic.getComponent(object);
 
@@ -276,7 +308,8 @@
 
         CharSequence entryTitle = cachingLogic.getLabel(object);
         if (entryTitle == null) {
-            Log.d(TAG, "No label returned from caching logic instance: " + cachingLogic);
+            Log.wtf(TAG, "No label returned from caching logic instance: " + cachingLogic);
+            entryTitle = "";
         }
         entry.title = entryTitle;
 
@@ -293,8 +326,8 @@
      * Updates {@param values} to contain versioning information and adds it to the DB.
      * @param values {@link ContentValues} containing icon & title
      */
-    private void addIconToDB(ContentValues values, ComponentName key,
-            PackageInfo info, long userSerial, long lastUpdateTime) {
+    private void addIconToDB(@NonNull final ContentValues values, @NonNull final ComponentName key,
+            @NonNull final PackageInfo info, final long userSerial, final long lastUpdateTime) {
         values.put(IconDB.COLUMN_COMPONENT, key.flattenToString());
         values.put(IconDB.COLUMN_USER, userSerial);
         values.put(IconDB.COLUMN_LAST_UPDATED, lastUpdateTime);
@@ -302,7 +335,8 @@
         mIconDb.insertOrReplace(values);
     }
 
-    public synchronized BitmapInfo getDefaultIcon(UserHandle user) {
+    @NonNull
+    public synchronized BitmapInfo getDefaultIcon(@NonNull final UserHandle user) {
         if (mDefaultIcon == null) {
             try (BaseIconFactory li = getIconFactory()) {
                 mDefaultIcon = li.makeDefaultIcon();
@@ -311,7 +345,8 @@
         return mDefaultIcon.withFlags(getUserFlagOpLocked(user));
     }
 
-    protected FlagOp getUserFlagOpLocked(UserHandle user) {
+    @NonNull
+    protected FlagOp getUserFlagOpLocked(@NonNull final UserHandle user) {
         int key = user.hashCode();
         int index;
         if ((index = mUserFlagOpMap.indexOfKey(key)) >= 0) {
@@ -325,7 +360,7 @@
         }
     }
 
-    public boolean isDefaultIcon(BitmapInfo icon, UserHandle user) {
+    public boolean isDefaultIcon(@NonNull final BitmapInfo icon, @NonNull final UserHandle user) {
         return getDefaultIcon(user).icon == icon.icon;
     }
 
@@ -333,10 +368,11 @@
      * Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
      * This method is not thread safe, it must be called from a synchronized method.
      */
+    @NonNull
     protected <T> CacheEntry cacheLocked(
-            @NonNull ComponentName componentName, @NonNull UserHandle user,
-            @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
-            boolean usePackageIcon, boolean useLowResIcon) {
+            @NonNull final ComponentName componentName, @NonNull final UserHandle user,
+            @NonNull final Supplier<T> infoProvider, @NonNull final CachingLogic<T> cachingLogic,
+            final boolean usePackageIcon, final boolean useLowResIcon) {
         return cacheLocked(
                 componentName,
                 user,
@@ -347,10 +383,12 @@
                 useLowResIcon);
     }
 
+    @NonNull
     protected <T> CacheEntry cacheLocked(
-            @NonNull ComponentName componentName, @NonNull UserHandle user,
-            @NonNull Supplier<T> infoProvider, @NonNull CachingLogic<T> cachingLogic,
-            @Nullable Cursor cursor, boolean usePackageIcon, boolean useLowResIcon) {
+            @NonNull final ComponentName componentName, @NonNull final UserHandle user,
+            @NonNull final Supplier<T> infoProvider, @NonNull final CachingLogic<T> cachingLogic,
+            @Nullable final Cursor cursor, final boolean usePackageIcon,
+            final boolean useLowResIcon) {
         assertWorkerThread();
         ComponentKey cacheKey = new ComponentKey(componentName, user);
         CacheEntry entry = mCache.get(cacheKey);
@@ -396,30 +434,28 @@
     /**
      * Fallback method for loading an icon bitmap.
      */
-    protected <T> void loadFallbackIcon(
-            T object, CacheEntry entry, @NonNull CachingLogic<T> cachingLogic,
-            boolean usePackageIcon, boolean usePackageTitle, @NonNull ComponentName componentName,
-            @NonNull UserHandle user) {
+    protected <T> void loadFallbackIcon(@Nullable final T object, @NonNull final CacheEntry entry,
+            @NonNull final CachingLogic<T> cachingLogic, final boolean usePackageIcon,
+            final boolean usePackageTitle, @NonNull final ComponentName componentName,
+            @NonNull final UserHandle user) {
         if (object != null) {
             entry.bitmap = cachingLogic.loadIcon(mContext, object);
         } else {
             if (usePackageIcon) {
                 CacheEntry packageEntry = getEntryForPackageLocked(
                         componentName.getPackageName(), user, false);
-                if (packageEntry != null) {
-                    if (DEBUG) Log.d(TAG, "using package default icon for " +
-                            componentName.toShortString());
-                    entry.bitmap = packageEntry.bitmap;
-                    entry.contentDescription = packageEntry.contentDescription;
+                if (DEBUG) Log.d(TAG, "using package default icon for " +
+                        componentName.toShortString());
+                entry.bitmap = packageEntry.bitmap;
+                entry.contentDescription = packageEntry.contentDescription;
 
-                    if (usePackageTitle) {
-                        entry.title = packageEntry.title;
-                    }
+                if (usePackageTitle) {
+                    entry.title = packageEntry.title;
                 }
             }
             if (entry.bitmap == null) {
-                if (DEBUG) Log.d(TAG, "using default icon for " +
-                        componentName.toShortString());
+                // TODO: entry.bitmap can never be null, so this should not happen at all.
+                Log.wtf(TAG, "using default icon for " + componentName.toShortString());
                 entry.bitmap = getDefaultIcon(user);
             }
         }
@@ -429,8 +465,8 @@
      * Fallback method for loading an app title.
      */
     protected <T> void loadFallbackTitle(
-            T object, CacheEntry entry, @NonNull CachingLogic<T> cachingLogic,
-            @NonNull UserHandle user) {
+            @NonNull final T object, @NonNull final CacheEntry entry,
+            @NonNull final CachingLogic<T> cachingLogic, @NonNull final UserHandle user) {
         entry.title = cachingLogic.getLabel(object);
         entry.contentDescription = mPackageManager.getUserBadgedLabel(
                 cachingLogic.getDescription(object, entry.title), user);
@@ -445,8 +481,9 @@
      * Adds a default package entry in the cache. This entry is not persisted and will be removed
      * when the cache is flushed.
      */
-    protected synchronized void cachePackageInstallInfo(String packageName, UserHandle user,
-            Bitmap icon, CharSequence title) {
+    protected synchronized void cachePackageInstallInfo(@NonNull final String packageName,
+            @NonNull final UserHandle user, @Nullable final Bitmap icon,
+            @Nullable final CharSequence title) {
         removeFromMemCacheLocked(packageName, user);
 
         ComponentKey cacheKey = getPackageKey(packageName, user);
@@ -469,7 +506,9 @@
         }
     }
 
-    private static ComponentKey getPackageKey(String packageName, UserHandle user) {
+    @NonNull
+    private static ComponentKey getPackageKey(@NonNull final String packageName,
+            @NonNull final UserHandle user) {
         ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME);
         return new ComponentKey(cn, user);
     }
@@ -478,8 +517,10 @@
      * Gets an entry for the package, which can be used as a fallback entry for various components.
      * This method is not thread safe, it must be called from a synchronized method.
      */
-    protected CacheEntry getEntryForPackageLocked(String packageName, UserHandle user,
-            boolean useLowResIcon) {
+    @WorkerThread
+    @NonNull
+    protected CacheEntry getEntryForPackageLocked(@NonNull final String packageName,
+            @NonNull final UserHandle user, final boolean useLowResIcon) {
         assertWorkerThread();
         ComponentKey cacheKey = getPackageKey(packageName, user);
         CacheEntry entry = mCache.get(cacheKey);
@@ -533,8 +574,8 @@
         return entry;
     }
 
-    protected boolean getEntryFromDBLocked(
-            ComponentKey cacheKey, CacheEntry entry, boolean lowRes) {
+    protected boolean getEntryFromDBLocked(@NonNull final ComponentKey cacheKey,
+            @NonNull final CacheEntry entry, final boolean lowRes) {
         Cursor c = null;
         Trace.beginSection("loadIconIndividually");
         try {
@@ -559,7 +600,8 @@
     }
 
     private boolean updateTitleAndIconLocked(
-            ComponentKey cacheKey, CacheEntry entry, Cursor c, boolean lowRes) {
+            @NonNull final ComponentKey cacheKey, @NonNull final CacheEntry entry,
+            @NonNull final Cursor c, final boolean lowRes) {
         // Set the alpha to be 255, so that we never have a wrong color
         entry.bitmap = BitmapInfo.of(LOW_RES_ICON,
                 setColorAlphaBound(c.getInt(IconDB.INDEX_COLOR), 255));
@@ -678,8 +720,10 @@
         }
     }
 
-    private ContentValues newContentValues(BitmapInfo bitmapInfo, String label,
-            String packageName, @Nullable String keywords) {
+    @NonNull
+    private ContentValues newContentValues(@NonNull final BitmapInfo bitmapInfo,
+            @NonNull final String label, @NonNull final String packageName,
+            @Nullable final String keywords) {
         ContentValues values = new ContentValues();
         if (bitmapInfo.canPersist()) {
             values.put(IconDB.COLUMN_ICON, flattenBitmap(bitmapInfo.icon));
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
index c12e9dc..8034d6e 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/CachingLogic.java
@@ -28,31 +28,36 @@
 
 public interface CachingLogic<T> {
 
-    ComponentName getComponent(T object);
+    @NonNull
+    ComponentName getComponent(@NonNull final T object);
 
-    UserHandle getUser(T object);
+    @NonNull
+    UserHandle getUser(@NonNull final T object);
 
-    CharSequence getLabel(T object);
+    @NonNull
+    CharSequence getLabel(@NonNull final T object);
 
-    default CharSequence getDescription(T object, CharSequence fallback) {
+    @NonNull
+    default CharSequence getDescription(@NonNull final T object,
+            @NonNull final CharSequence fallback) {
         return fallback;
     }
 
     @NonNull
-    BitmapInfo loadIcon(Context context, T object);
+    BitmapInfo loadIcon(@NonNull final Context context, @NonNull final T object);
 
     /**
      * Provides a option list of keywords to associate with this object
      */
     @Nullable
-    default String getKeywords(T object, LocaleList localeList) {
+    default String getKeywords(@NonNull final T object, @NonNull final LocaleList localeList) {
         return null;
     }
 
     /**
      * Returns the timestamp the entry was last updated in cache.
      */
-    default long getLastUpdatedTime(T object, PackageInfo info) {
+    default long getLastUpdatedTime(@Nullable final T object, @NonNull final PackageInfo info) {
         return info.lastUpdateTime;
     }
 
diff --git a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
index 9e1ad7b..aec1cdd 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/cache/IconCacheUpdateHandler.java
@@ -180,7 +180,8 @@
                 long updateTime = c.getLong(indexLastUpdate);
                 int version = c.getInt(indexVersion);
                 T app = componentMap.remove(component);
-                if (version == info.versionCode && updateTime == info.lastUpdateTime
+                if (version == info.versionCode
+                        && updateTime == cachingLogic.getLastUpdatedTime(app, info)
                         && TextUtils.equals(c.getString(systemStateIndex),
                                 mIconCache.getIconSystemState(info.packageName))) {
 
diff --git a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
index cc4ad7b..63ba887 100644
--- a/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
+++ b/iconloaderlib/src_full_lib/com/android/launcher3/icons/SimpleIconCache.java
@@ -32,6 +32,8 @@
 import android.os.UserManager;
 import android.util.SparseLongArray;
 
+import androidx.annotation.NonNull;
+
 import com.android.launcher3.icons.cache.BaseIconCache;
 
 /**
@@ -64,7 +66,7 @@
     }
 
     @Override
-    protected long getSerialNumberForUser(UserHandle user) {
+    protected long getSerialNumberForUser(@NonNull UserHandle user) {
         synchronized (mUserSerialMap) {
             int index = mUserSerialMap.indexOfKey(user.getIdentifier());
             if (index >= 0) {
@@ -83,10 +85,11 @@
     }
 
     @Override
-    protected boolean isInstantApp(ApplicationInfo info) {
+    protected boolean isInstantApp(@NonNull ApplicationInfo info) {
         return info.isInstantApp();
     }
 
+    @NonNull
     @Override
     public BaseIconFactory getIconFactory() {
         return IconFactory.obtain(mContext);
diff --git a/searchuilib/build.gradle b/searchuilib/build.gradle
index 3bf65fe..6b5027b 100644
--- a/searchuilib/build.gradle
+++ b/searchuilib/build.gradle
@@ -1,12 +1,14 @@
-apply plugin: 'com.android.library'
+plugins {
+    id 'com.android.library'
+}
 
 android {
-    compileSdkVersion COMPILE_SDK
-    buildToolsVersion BUILD_TOOLS_VERSION
+    compileSdk TARGET_SDK.toInteger()
+    buildToolsVersion = BUILD_TOOLS_VERSION
 
     defaultConfig {
-        minSdkVersion 25
-        targetSdkVersion 28
+        minSdkVersion TARGET_SDK.toInteger()
+        targetSdkVersion TARGET_SDK.toInteger()
     }
 
     sourceSets {
@@ -31,5 +33,5 @@
 }
 
 dependencies {
-    implementation "androidx.core:core:${ANDROID_X_VERSION}"
+    implementation "androidx.core:core:+"
 }
diff --git a/searchuilib/src/com/android/app/search/LayoutType.java b/searchuilib/src/com/android/app/search/LayoutType.java
index d7c28ab..072c09b 100644
--- a/searchuilib/src/com/android/app/search/LayoutType.java
+++ b/searchuilib/src/com/android/app/search/LayoutType.java
@@ -47,6 +47,9 @@
     public static final String SMALL_ICON_HORIZONTAL_TEXT = "short_icon_row";
     public static final String SMALL_ICON_HORIZONTAL_TEXT_THUMBNAIL = "short_icon_row_thumbnail";
 
+    // This layout contains a series of thumbnails (currently up to 3 per row)
+    public static final String THUMBNAIL_CONTAINER = "thumbnail_container";
+
     // This layout creates square thumbnail image (currently 3 column)
     public static final String THUMBNAIL = "thumbnail";