Making LauncherIcons thread safe
Creating a pool of LauncherIcons so that they can be used from multiple threads
Change-Id: Idc7b5ddb47b6e338a5389f3c4faa6f63de108c72
diff --git a/src/com/android/launcher3/AppInfo.java b/src/com/android/launcher3/AppInfo.java
index 9796d18..4d1bedc 100644
--- a/src/com/android/launcher3/AppInfo.java
+++ b/src/com/android/launcher3/AppInfo.java
@@ -110,7 +110,7 @@
info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
- if (FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO
+ if (Utilities.ATLEAST_OREO
&& appInfo.targetSdkVersion >= Build.VERSION_CODES.O
&& Process.myUserHandle().equals(lai.getUser())) {
// The icon for a non-primary user is badged, hence it's not exactly an adaptive icon.
diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java
index 9775955..469b8bb 100644
--- a/src/com/android/launcher3/AutoInstallsLayout.java
+++ b/src/com/android/launcher3/AutoInstallsLayout.java
@@ -437,9 +437,11 @@
}
// Auto installs should always support the current platform version.
+ LauncherIcons li = LauncherIcons.obtain(mContext);
mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap(
- LauncherIcons.createBadgedIconBitmap(
- icon, Process.myUserHandle(), mContext, VERSION.SDK_INT).icon));
+ li.createBadgedIconBitmap(icon, Process.myUserHandle(), VERSION.SDK_INT).icon));
+ li.recycle();
+
mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId));
mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId));
diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java
index baa60b0..a5ca3ee 100644
--- a/src/com/android/launcher3/IconCache.java
+++ b/src/com/android/launcher3/IconCache.java
@@ -193,8 +193,10 @@
}
protected BitmapInfo makeDefaultIcon(UserHandle user) {
- Drawable unbadged = getFullResDefaultActivityIcon();
- return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, VERSION.SDK_INT);
+ try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+ return li.createBadgedIconBitmap(
+ getFullResDefaultActivityIcon(), user, VERSION.SDK_INT);
+ }
}
/**
@@ -378,8 +380,10 @@
}
if (entry == null) {
entry = new CacheEntry();
- LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
- mContext, app.getApplicationInfo().targetSdkVersion).applyTo(entry);
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(),
+ app.getApplicationInfo().targetSdkVersion).applyTo(entry);
+ li.recycle();
}
entry.title = app.getLabel();
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser());
@@ -535,9 +539,10 @@
providerFetchedOnce = true;
if (info != null) {
- LauncherIcons.createBadgedIconBitmap(
- getFullResIcon(info), info.getUser(), mContext,
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(),
info.getApplicationInfo().targetSdkVersion).applyTo(entry);
+ li.recycle();
} else {
if (usePackageIcon) {
CacheEntry packageEntry = getEntryForPackageLocked(
@@ -596,7 +601,9 @@
entry.title = title;
}
if (icon != null) {
- LauncherIcons.createIconBitmap(icon, mContext).applyTo(entry);
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ li.createIconBitmap(icon).applyTo(entry);
+ li.recycle();
}
if (!TextUtils.isEmpty(title) && entry.icon != null) {
mCache.put(cacheKey, entry);
@@ -633,14 +640,17 @@
throw new NameNotFoundException("ApplicationInfo is null");
}
+ LauncherIcons li = LauncherIcons.obtain(mContext);
// Load the full res icon for the application, but if useLowResIcon is set, then
// only keep the low resolution icon instead of the larger full-sized icon
- BitmapInfo iconInfo = LauncherIcons.createBadgedIconBitmap(
- appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion);
+ BitmapInfo iconInfo = li.createBadgedIconBitmap(
+ appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion);
if (mInstantAppResolver.isInstantApp(appInfo)) {
- LauncherIcons.badgeWithDrawable(iconInfo.icon,
- mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext);
+ li.badgeWithDrawable(iconInfo.icon,
+ mContext.getDrawable(R.drawable.ic_instant_app_badge));
}
+ li.recycle();
+
Bitmap lowResIcon = generateLowResIcon(iconInfo.icon);
entry.title = appInfo.loadLabel(mPackageManager);
entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user);
@@ -776,10 +786,7 @@
}
private static final class IconDB extends SQLiteCacheHelper {
- private final static int DB_VERSION = 18;
-
- private final static int RELEASE_VERSION = DB_VERSION +
- (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1);
+ private final static int RELEASE_VERSION = 20;
private final static String TABLE_NAME = "icons";
private final static String COLUMN_ROWID = "rowid";
diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java
index c476421..fe8a841 100644
--- a/src/com/android/launcher3/InstallShortcutReceiver.java
+++ b/src/com/android/launcher3/InstallShortcutReceiver.java
@@ -498,7 +498,9 @@
return Pair.create((ItemInfo) si, (Object) activityInfo);
} else if (shortcutInfo != null) {
ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext);
- LauncherIcons.createShortcutIcon(shortcutInfo, mContext).applyTo(si);
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ li.createShortcutIcon(shortcutInfo).applyTo(si);
+ li.recycle();
return Pair.create((ItemInfo) si, (Object) shortcutInfo);
} else if (providerInfo != null) {
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo
@@ -643,15 +645,18 @@
info.user = Process.myUserHandle();
BitmapInfo iconInfo = null;
+ LauncherIcons li = LauncherIcons.obtain(app.getContext());
if (bitmap instanceof Bitmap) {
- iconInfo = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext());
+ iconInfo = li.createIconBitmap((Bitmap) bitmap);
} else {
Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE);
if (extra instanceof Intent.ShortcutIconResource) {
info.iconResource = (Intent.ShortcutIconResource) extra;
- iconInfo = LauncherIcons.createIconBitmap(info.iconResource, app.getContext());
+ iconInfo = li.createIconBitmap(info.iconResource);
}
}
+ li.recycle();
+
if (iconInfo == null) {
iconInfo = app.getIconCache().getDefaultIcon(info.user);
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index ea4b280..1b169f5 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -620,7 +620,9 @@
@Override
public ShortcutInfo get() {
si.updateFromDeepShortcutInfo(info, mApp.getContext());
- LauncherIcons.createShortcutIcon(info, mApp.getContext()).applyTo(si);
+ LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+ li.createShortcutIcon(info).applyTo(si);
+ li.recycle();
return si;
}
});
diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java
index bdfeae1..a658d58 100644
--- a/src/com/android/launcher3/WidgetPreviewLoader.java
+++ b/src/com/android/launcher3/WidgetPreviewLoader.java
@@ -469,8 +469,11 @@
}
RectF boxRect = drawBoxWithShadow(c, size, size);
- Bitmap icon = LauncherIcons.createScaledBitmapWithoutShadow(
- mutateOnMainThread(info.getFullResIcon(mIconCache)), mContext, 0);
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ Bitmap icon = li.createScaledBitmapWithoutShadow(
+ mutateOnMainThread(info.getFullResIcon(mIconCache)), 0);
+ li.recycle();
+
Rect src = new Rect(0, 0, icon.getWidth(), icon.getHeight());
boxRect.set(0, 0, iconSize, iconSize);
diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
index 5cd90b1..173d0d1 100644
--- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
+++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java
@@ -137,7 +137,9 @@
ShortcutInfoCompat compat = new ShortcutInfoCompat(request.getShortcutInfo());
ShortcutInfo info = new ShortcutInfo(compat, context);
// Apply the unbadged icon and fetch the actual icon asynchronously.
- LauncherIcons.createShortcutIcon(compat, context, false /* badged */).applyTo(info);
+ LauncherIcons li = LauncherIcons.obtain(context);
+ li.createShortcutIcon(compat, false /* badged */).applyTo(info);
+ li.recycle();
LauncherAppState.getInstance(context).getModel()
.updateAndBindShortcutInfo(info, compat);
return info;
diff --git a/src/com/android/launcher3/config/BaseFlags.java b/src/com/android/launcher3/config/BaseFlags.java
index 0d92d45..18797a4 100644
--- a/src/com/android/launcher3/config/BaseFlags.java
+++ b/src/com/android/launcher3/config/BaseFlags.java
@@ -31,8 +31,6 @@
public static final boolean IS_DOGFOOD_BUILD = false;
public static final String AUTHORITY = "com.android.launcher3.settings".intern();
- // Custom flags go below this
- public static boolean LAUNCHER3_DISABLE_ICON_NORMALIZATION = false;
// When enabled allows to use any point on the fast scrollbar to start dragging.
public static final boolean LAUNCHER3_DIRECT_SCROLL = true;
// When enabled the promise icon is visible in all apps while installation an app.
@@ -46,10 +44,6 @@
public static final boolean QSB_ON_FIRST_SCREEN = true;
// When enabled the all-apps icon is not added to the hotseat.
public static final boolean NO_ALL_APPS_ICON = true;
- // When enabled, icons not supporting {@link AdaptiveIconDrawable} will be wrapped in {@link FixedScaleDrawable}.
- public static final boolean LEGACY_ICON_TREATMENT = true;
- // When enabled, adaptive icons would have shadows baked when being stored to icon cache.
- public static final boolean ADAPTIVE_ICON_SHADOW = true;
// When enabled, app discovery will be enabled if service is implemented
public static final boolean DISCOVERY_ENABLED = false;
diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java
index 11ff88f..9732261 100644
--- a/src/com/android/launcher3/dragndrop/DragView.java
+++ b/src/com/android/launcher3/dragndrop/DragView.java
@@ -224,8 +224,10 @@
mBadge = getBadge(info, appState, outObj[0]);
mBadge.setBounds(badgeBounds);
+ LauncherIcons li = LauncherIcons.obtain(mLauncher);
Utilities.scaleRectAboutCenter(bounds,
- IconNormalizer.getInstance(mLauncher).getScale(dr, null, null, null));
+ li.getNormalizer().getScale(dr, null, null, null));
+ li.recycle();
AdaptiveIconDrawable adaptiveIcon = (AdaptiveIconDrawable) dr;
// Shrink very tiny bit so that the clip path is smaller than the original bitmap
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index d4c396a..64f96d5 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -453,9 +453,8 @@
*/
@SuppressLint("InflateParams")
static Folder fromXml(Launcher launcher) {
- return (Folder) launcher.getLayoutInflater().inflate(
- FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION
- ? R.layout.user_folder : R.layout.user_folder_icon_normalized, null);
+ return (Folder) launcher.getLayoutInflater()
+ .inflate(R.layout.user_folder_icon_normalized, null);
}
private void startAnimation(final AnimatorSet a) {
diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/graphics/IconNormalizer.java
index 5ee6a30..bd20c87 100644
--- a/src/com/android/launcher3/graphics/IconNormalizer.java
+++ b/src/com/android/launcher3/graphics/IconNormalizer.java
@@ -37,10 +37,7 @@
import com.android.launcher3.Utilities;
import com.android.launcher3.dragndrop.FolderAdaptiveIcon;
-import java.io.File;
-import java.io.FileOutputStream;
import java.nio.ByteBuffer;
-import java.util.Random;
public class IconNormalizer {
@@ -64,9 +61,6 @@
private static final float PIXEL_DIFF_PERCENTAGE_THRESHOLD = 0.005f;
private static final float SCALE_NOT_INITIALIZED = 0;
- private static final Object LOCK = new Object();
- private static IconNormalizer sIconNormalizer;
-
private final int mMaxSize;
private final Bitmap mBitmap;
private final Bitmap mBitmapARGB;
@@ -88,11 +82,8 @@
private final Paint mPaintIcon;
private final Canvas mCanvasARGB;
- private final File mDir;
- private int mFileId;
- private final Random mRandom;
-
- private IconNormalizer(Context context) {
+ /** package private **/
+ IconNormalizer(Context context) {
// Use twice the icon size as maximum size to avoid scaling down twice.
mMaxSize = LauncherAppState.getIDP(context).iconBitmapSize * 2;
mBitmap = Bitmap.createBitmap(mMaxSize, mMaxSize, Bitmap.Config.ALPHA_8);
@@ -124,9 +115,6 @@
mMatrix = new Matrix();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
-
- mDir = context.getExternalFilesDir(null);
- mRandom = new Random();
}
/**
@@ -148,18 +136,9 @@
// Condition 2:
// Actual icon (white) and the fitted shape (e.g., circle)(red) XOR operation
// should generate transparent image, if the actual icon is equivalent to the shape.
- mFileId = mRandom.nextInt();
mBitmapARGB.eraseColor(Color.TRANSPARENT);
mCanvasARGB.drawBitmap(mBitmap, 0, 0, mPaintIcon);
- if (DEBUG) {
- final File beforeFile = new File(mDir, "isShape" + mFileId + "_before.png");
- try {
- mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
- new FileOutputStream(beforeFile));
- } catch (Exception e) {}
- }
-
// Fit the shape within the icon's bounding box
mMatrix.reset();
mMatrix.setScale(mBounds.width(), mBounds.height());
@@ -172,24 +151,8 @@
// DST_OUT operation around the mask path outline
mCanvasARGB.drawPath(maskPath, mPaintMaskShapeOutline);
- boolean isTrans = isTransparentBitmap(mBitmapARGB);
- if (DEBUG) {
- final File afterFile = new File(mDir,
- "isShape" + mFileId + "_after_" + isTrans + ".png");
- try {
- mBitmapARGB.compress(Bitmap.CompressFormat.PNG, 100,
- new FileOutputStream(afterFile));
- } catch (Exception e) {}
- }
-
// Check if the result is almost transparent
- if (!isTrans) {
- if (DEBUG) {
- Log.d(TAG, "Not same as mask shape");
- }
- return false;
- }
- return true;
+ return isTransparentBitmap(mBitmapARGB);
}
/**
@@ -203,19 +166,13 @@
mBounds.left, mBounds.top,
w, h);
int sum = 0;
- for (int i = 0; i < w * h; i++) {
+ for (int i = w * h - 1; i >= 0; i--) {
if(Color.alpha(mPixelsARGB[i]) > MIN_VISIBLE_ALPHA) {
sum++;
}
}
float percentageDiffPixels = ((float) sum) / (mBounds.width() * mBounds.height());
- boolean transparentImage = percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
- if (DEBUG) {
- Log.d(TAG,
- "Total # pixel that is different (id=" + mFileId + "):" + percentageDiffPixels
- + "=" + sum + "/" + mBounds.width() * mBounds.height());
- }
- return transparentImage;
+ return percentageDiffPixels < PIXEL_DIFF_PERCENTAGE_THRESHOLD;
}
/**
@@ -416,13 +373,4 @@
last = i;
}
}
-
- public static IconNormalizer getInstance(Context context) {
- synchronized (LOCK) {
- if (sIconNormalizer == null) {
- sIconNormalizer = new IconNormalizer(context);
- }
- }
- return sIconNormalizer;
- }
}
diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java
index fdb6313..0c9f4d9 100644
--- a/src/com/android/launcher3/graphics/LauncherIcons.java
+++ b/src/com/android/launcher3/graphics/LauncherIcons.java
@@ -16,7 +16,11 @@
package com.android.launcher3.graphics;
-import android.annotation.TargetApi;
+import static android.graphics.Paint.DITHER_FLAG;
+import static android.graphics.Paint.FILTER_BITMAP_FLAG;
+
+import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR;
+
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -25,7 +29,6 @@
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
-import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Rect;
import android.graphics.RectF;
@@ -41,11 +44,11 @@
import com.android.launcher3.AppInfo;
import com.android.launcher3.FastBitmapDrawable;
import com.android.launcher3.IconCache;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfoWithIcon;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.PackageItemInfo;
import com.android.launcher3.shortcuts.DeepShortcutManager;
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
@@ -56,32 +59,95 @@
/**
* Helper methods for generating various launcher icons
*/
-public class LauncherIcons {
+public class LauncherIcons implements AutoCloseable {
- private static final Rect sOldBounds = new Rect();
- private static final Canvas sCanvas = new Canvas();
+ public static final Object sPoolSync = new Object();
+ private static LauncherIcons sPool;
- static {
- sCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
- Paint.FILTER_BITMAP_FLAG));
+ /**
+ * Return a new Message instance from the global pool. Allows us to
+ * avoid allocating new objects in many cases.
+ */
+ public static LauncherIcons obtain(Context context) {
+ synchronized (sPoolSync) {
+ if (sPool != null) {
+ LauncherIcons m = sPool;
+ sPool = m.next;
+ m.next = null;
+ return m;
+ }
+ }
+ return new LauncherIcons(context);
+ }
+
+ /**
+ * Recycles a LauncherIcons that may be in-use.
+ */
+ public void recycle() {
+ synchronized (sPoolSync) {
+ next = sPool;
+ sPool = this;
+ }
+ }
+
+ @Override
+ public void close() {
+ recycle();
+ }
+
+ private final Rect mOldBounds = new Rect();
+ private final Context mContext;
+ private final Canvas mCanvas;
+ private final PackageManager mPm;
+
+ private final int mFillResIconDpi;
+ private final int mIconBitmapSize;
+
+ private IconNormalizer mNormalizer;
+ private ShadowGenerator mShadowGenerator;
+
+ // sometimes we store linked lists of these things
+ private LauncherIcons next;
+
+ private LauncherIcons(Context context) {
+ mContext = context.getApplicationContext();
+ mPm = mContext.getPackageManager();
+
+ InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
+ mFillResIconDpi = idp.fillResIconDpi;
+ mIconBitmapSize = idp.iconBitmapSize;
+
+ mCanvas = new Canvas();
+ mCanvas.setDrawFilter(new PaintFlagsDrawFilter(DITHER_FLAG, FILTER_BITMAP_FLAG));
+ }
+
+ public ShadowGenerator getShadowGenerator() {
+ if (mShadowGenerator == null) {
+ mShadowGenerator = new ShadowGenerator(mContext);
+ }
+ return mShadowGenerator;
+ }
+
+ public IconNormalizer getNormalizer() {
+ if (mNormalizer == null) {
+ mNormalizer = new IconNormalizer(mContext);
+ }
+ return mNormalizer;
}
/**
* Returns a bitmap suitable for the all apps view. If the package or the resource do not
* exist, it returns null.
*/
- public static BitmapInfo createIconBitmap(ShortcutIconResource iconRes, Context context) {
- PackageManager packageManager = context.getPackageManager();
- // the resource
+ public BitmapInfo createIconBitmap(ShortcutIconResource iconRes) {
try {
- Resources resources = packageManager.getResourcesForApplication(iconRes.packageName);
+ Resources resources = mPm.getResourcesForApplication(iconRes.packageName);
if (resources != null) {
final int id = resources.getIdentifier(iconRes.resourceName, null, null);
// do not stamp old legacy shortcuts as the app may have already forgotten about it
- return createBadgedIconBitmap(resources.getDrawableForDensity(
- id, LauncherAppState.getIDP(context).fillResIconDpi),
+ return createBadgedIconBitmap(
+ resources.getDrawableForDensity(id, mFillResIconDpi),
Process.myUserHandle() /* only available on primary user */,
- context,
0 /* do not apply legacy treatment */);
}
} catch (Exception e) {
@@ -93,13 +159,12 @@
/**
* Returns a bitmap which is of the appropriate size to be displayed as an icon
*/
- public static BitmapInfo createIconBitmap(Bitmap icon, Context context) {
- final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
- if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) {
+ public BitmapInfo createIconBitmap(Bitmap icon) {
+ if (mIconBitmapSize == icon.getWidth() && mIconBitmapSize == icon.getHeight()) {
return BitmapInfo.fromBitmap(icon);
}
return BitmapInfo.fromBitmap(
- createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f));
+ createIconBitmap(new BitmapDrawable(mContext.getResources(), icon), 1f));
}
/**
@@ -107,51 +172,24 @@
* view or workspace. The icon is badged for {@param user}.
* The bitmap is also visually normalized with other icons.
*/
- public static BitmapInfo createBadgedIconBitmap(
- Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) {
-
- IconNormalizer normalizer;
- float scale = 1f;
- if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
- normalizer = IconNormalizer.getInstance(context);
- if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
- boolean[] outShape = new boolean[1];
- AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
- context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
- dr.setBounds(0, 0, 1, 1);
- scale = normalizer.getScale(icon, null, dr.getIconMask(), outShape);
- if (FeatureFlags.LEGACY_ICON_TREATMENT &&
- !outShape[0]){
- Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
- if (wrappedIcon != icon) {
- icon = wrappedIcon;
- scale = normalizer.getScale(icon, null, null, null);
- }
- }
- } else {
- scale = normalizer.getScale(icon, null, null, null);
- }
- }
- Bitmap bitmap = createIconBitmap(icon, context, scale);
- if (FeatureFlags.ADAPTIVE_ICON_SHADOW && Utilities.ATLEAST_OREO &&
- icon instanceof AdaptiveIconDrawable) {
- synchronized (sCanvas) {
- sCanvas.setBitmap(bitmap);
- ShadowGenerator.getInstance(context).recreateIcon(
- Bitmap.createBitmap(bitmap), sCanvas);
- sCanvas.setBitmap(null);
- }
+ public BitmapInfo createBadgedIconBitmap(Drawable icon, UserHandle user, int iconAppTargetSdk) {
+ float[] scale = new float[1];
+ icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, null, scale);
+ Bitmap bitmap = createIconBitmap(icon, scale[0]);
+ if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+ mCanvas.setBitmap(bitmap);
+ getShadowGenerator().recreateIcon(Bitmap.createBitmap(bitmap), mCanvas);
+ mCanvas.setBitmap(null);
}
final Bitmap result;
if (user != null && !Process.myUserHandle().equals(user)) {
BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap);
- Drawable badged = context.getPackageManager().getUserBadgedIcon(
- drawable, user);
+ Drawable badged = mPm.getUserBadgedIcon(drawable, user);
if (badged instanceof BitmapDrawable) {
result = ((BitmapDrawable) badged).getBitmap();
} else {
- result = createIconBitmap(badged, context, 1f);
+ result = createIconBitmap(badged, 1f);
}
} else {
result = bitmap;
@@ -163,170 +201,134 @@
* Creates a normalized bitmap suitable for the all apps view. The bitmap is also visually
* normalized with other icons and has enough spacing to add shadow.
*/
- public static Bitmap createScaledBitmapWithoutShadow(
- Drawable icon, Context context, int iconAppTargetSdk) {
+ public Bitmap createScaledBitmapWithoutShadow(Drawable icon, int iconAppTargetSdk) {
RectF iconBounds = new RectF();
- IconNormalizer normalizer;
+ float[] scale = new float[1];
+ icon = normalizeAndWrapToAdaptiveIcon(icon, iconAppTargetSdk, iconBounds, scale);
+ return createIconBitmap(icon,
+ Math.min(scale[0], ShadowGenerator.getScaleForBounds(iconBounds)));
+ }
+
+ private Drawable normalizeAndWrapToAdaptiveIcon(Drawable icon, int iconAppTargetSdk,
+ RectF outIconBounds, float[] outScale) {
float scale = 1f;
- if (!FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION) {
- normalizer = IconNormalizer.getInstance(context);
- if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
- boolean[] outShape = new boolean[1];
- AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
- context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
- dr.setBounds(0, 0, 1, 1);
- scale = normalizer.getScale(icon, iconBounds, dr.getIconMask(), outShape);
- if (Utilities.ATLEAST_OREO && FeatureFlags.LEGACY_ICON_TREATMENT &&
- !outShape[0]) {
- Drawable wrappedIcon = wrapToAdaptiveIconDrawable(context, icon, scale);
- if (wrappedIcon != icon) {
- icon = wrappedIcon;
- scale = normalizer.getScale(icon, iconBounds, null, null);
- }
- }
- } else {
- scale = normalizer.getScale(icon, iconBounds, null, null);
+ if (Utilities.ATLEAST_OREO && iconAppTargetSdk >= Build.VERSION_CODES.O) {
+ boolean[] outShape = new boolean[1];
+ AdaptiveIconDrawable dr = (AdaptiveIconDrawable)
+ mContext.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
+ dr.setBounds(0, 0, 1, 1);
+ scale = getNormalizer().getScale(icon, outIconBounds, dr.getIconMask(), outShape);
+ if (Utilities.ATLEAST_OREO && !outShape[0] && !(icon instanceof AdaptiveIconDrawable)) {
+ FixedScaleDrawable fsd = ((FixedScaleDrawable) dr.getForeground());
+ fsd.setDrawable(icon);
+ fsd.setScale(scale);
+ icon = dr;
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
}
-
+ } else {
+ scale = getNormalizer().getScale(icon, outIconBounds, null, null);
}
- scale = Math.min(scale, ShadowGenerator.getScaleForBounds(iconBounds));
- return createIconBitmap(icon, context, scale);
+
+ outScale[0] = scale;
+ return icon;
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
- public static void badgeWithDrawable(Bitmap target, Drawable badge, Context context) {
- synchronized (sCanvas) {
- sCanvas.setBitmap(target);
- badgeWithDrawable(sCanvas, badge, context);
- sCanvas.setBitmap(null);
- }
+ public void badgeWithDrawable(Bitmap target, Drawable badge) {
+ mCanvas.setBitmap(target);
+ badgeWithDrawable(mCanvas, badge);
+ mCanvas.setBitmap(null);
}
/**
* Adds the {@param badge} on top of {@param target} using the badge dimensions.
*/
- private static void badgeWithDrawable(Canvas target, Drawable badge, Context context) {
- int badgeSize = context.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
- int iconSize = LauncherAppState.getIDP(context).iconBitmapSize;
- badge.setBounds(iconSize - badgeSize, iconSize - badgeSize, iconSize, iconSize);
+ private void badgeWithDrawable(Canvas target, Drawable badge) {
+ int badgeSize = mContext.getResources().getDimensionPixelSize(R.dimen.profile_badge_size);
+ badge.setBounds(mIconBitmapSize - badgeSize, mIconBitmapSize - badgeSize,
+ mIconBitmapSize, mIconBitmapSize);
badge.draw(target);
}
/**
* @param scale the scale to apply before drawing {@param icon} on the canvas
*/
- private static Bitmap createIconBitmap(Drawable icon, Context context, float scale) {
- synchronized (sCanvas) {
- final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize;
- int width = iconBitmapSize;
- int height = iconBitmapSize;
+ private Bitmap createIconBitmap(Drawable icon, float scale) {
+ int width = mIconBitmapSize;
+ int height = mIconBitmapSize;
- if (icon instanceof PaintDrawable) {
- PaintDrawable painter = (PaintDrawable) icon;
- painter.setIntrinsicWidth(width);
- painter.setIntrinsicHeight(height);
- } else if (icon instanceof BitmapDrawable) {
- // Ensure the bitmap has a density.
- BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
- Bitmap bitmap = bitmapDrawable.getBitmap();
- if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
- bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
- }
+ if (icon instanceof PaintDrawable) {
+ PaintDrawable painter = (PaintDrawable) icon;
+ painter.setIntrinsicWidth(width);
+ painter.setIntrinsicHeight(height);
+ } else if (icon instanceof BitmapDrawable) {
+ // Ensure the bitmap has a density.
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
+ Bitmap bitmap = bitmapDrawable.getBitmap();
+ if (bitmap != null && bitmap.getDensity() == Bitmap.DENSITY_NONE) {
+ bitmapDrawable.setTargetDensity(mContext.getResources().getDisplayMetrics());
}
-
- int sourceWidth = icon.getIntrinsicWidth();
- int sourceHeight = icon.getIntrinsicHeight();
- if (sourceWidth > 0 && sourceHeight > 0) {
- // Scale the icon proportionally to the icon dimensions
- final float ratio = (float) sourceWidth / sourceHeight;
- if (sourceWidth > sourceHeight) {
- height = (int) (width / ratio);
- } else if (sourceHeight > sourceWidth) {
- width = (int) (height * ratio);
- }
- }
- // no intrinsic size --> use default size
- int textureWidth = iconBitmapSize;
- int textureHeight = iconBitmapSize;
-
- Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
- Bitmap.Config.ARGB_8888);
- final Canvas canvas = sCanvas;
- canvas.setBitmap(bitmap);
-
- final int left = (textureWidth-width) / 2;
- final int top = (textureHeight-height) / 2;
-
- sOldBounds.set(icon.getBounds());
- if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
- int offset = Math.max((int)(ShadowGenerator.BLUR_FACTOR * iconBitmapSize),
- Math.min(left, top));
- int size = Math.max(width, height);
- icon.setBounds(offset, offset, size, size);
- } else {
- icon.setBounds(left, top, left+width, top+height);
- }
- canvas.save(Canvas.MATRIX_SAVE_FLAG);
- canvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
- icon.draw(canvas);
- canvas.restore();
- icon.setBounds(sOldBounds);
- canvas.setBitmap(null);
-
- return bitmap;
- }
- }
-
- /**
- * If the platform is running O but the app is not providing AdaptiveIconDrawable, then
- * shrink the legacy icon and set it as foreground. Use color drawable as background to
- * create AdaptiveIconDrawable.
- */
- @TargetApi(Build.VERSION_CODES.O)
- private static Drawable wrapToAdaptiveIconDrawable(
- Context context, Drawable drawable, float scale) {
- if (!(FeatureFlags.LEGACY_ICON_TREATMENT && Utilities.ATLEAST_OREO)) {
- return drawable;
}
- try {
- if (!(drawable instanceof AdaptiveIconDrawable)) {
- AdaptiveIconDrawable iconWrapper = (AdaptiveIconDrawable)
- context.getDrawable(R.drawable.adaptive_icon_drawable_wrapper).mutate();
- FixedScaleDrawable fsd = ((FixedScaleDrawable) iconWrapper.getForeground());
- fsd.setDrawable(drawable);
- fsd.setScale(scale);
- return iconWrapper;
+ int sourceWidth = icon.getIntrinsicWidth();
+ int sourceHeight = icon.getIntrinsicHeight();
+ if (sourceWidth > 0 && sourceHeight > 0) {
+ // Scale the icon proportionally to the icon dimensions
+ final float ratio = (float) sourceWidth / sourceHeight;
+ if (sourceWidth > sourceHeight) {
+ height = (int) (width / ratio);
+ } else if (sourceHeight > sourceWidth) {
+ width = (int) (height * ratio);
}
- } catch (Exception e) {
- return drawable;
}
- return drawable;
+ // no intrinsic size --> use default size
+ int textureWidth = mIconBitmapSize;
+ int textureHeight = mIconBitmapSize;
+
+ Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
+ Bitmap.Config.ARGB_8888);
+ mCanvas.setBitmap(bitmap);
+
+ final int left = (textureWidth-width) / 2;
+ final int top = (textureHeight-height) / 2;
+
+ mOldBounds.set(icon.getBounds());
+ if (Utilities.ATLEAST_OREO && icon instanceof AdaptiveIconDrawable) {
+ int offset = Math.max((int)(BLUR_FACTOR * textureWidth), Math.min(left, top));
+ int size = Math.max(width, height);
+ icon.setBounds(offset, offset, size, size);
+ } else {
+ icon.setBounds(left, top, left+width, top+height);
+ }
+ mCanvas.save(Canvas.MATRIX_SAVE_FLAG);
+ mCanvas.scale(scale, scale, textureWidth / 2, textureHeight / 2);
+ icon.draw(mCanvas);
+ mCanvas.restore();
+ icon.setBounds(mOldBounds);
+ mCanvas.setBitmap(null);
+
+ return bitmap;
}
- public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) {
- return createShortcutIcon(shortcutInfo, context, true /* badged */);
+ public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo) {
+ return createShortcutIcon(shortcutInfo, true /* badged */);
}
- public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
- boolean badged) {
- return createShortcutIcon(shortcutInfo, context, badged, null);
+ public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, boolean badged) {
+ return createShortcutIcon(shortcutInfo, badged, null);
}
- public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context,
+ public BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo,
boolean badged, @Nullable Provider<Bitmap> fallbackIconProvider) {
- LauncherAppState app = LauncherAppState.getInstance(context);
- Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context)
- .getShortcutIconDrawable(shortcutInfo,
- app.getInvariantDeviceProfile().fillResIconDpi);
- IconCache cache = app.getIconCache();
+ Drawable unbadgedDrawable = DeepShortcutManager.getInstance(mContext)
+ .getShortcutIconDrawable(shortcutInfo, mFillResIconDpi);
+ IconCache cache = LauncherAppState.getInstance(mContext).getIconCache();
Bitmap unbadgedBitmap = null;
if (unbadgedDrawable != null) {
- unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow(
- unbadgedDrawable, context, 0);
+ unbadgedBitmap = createScaledBitmapWithoutShadow(unbadgedDrawable, 0);
} else {
if (fallbackIconProvider != null) {
unbadgedBitmap = fallbackIconProvider.get();
@@ -338,20 +340,18 @@
BitmapInfo result = new BitmapInfo();
if (!badged) {
- result.color = Themes.getColorAccent(context);
+ result.color = Themes.getColorAccent(mContext);
result.icon = unbadgedBitmap;
return result;
}
- int size = app.getInvariantDeviceProfile().iconBitmapSize;
-
final Bitmap unbadgedfinal = unbadgedBitmap;
final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache);
result.color = badge.iconColor;
- result.icon = UiFactory.createFromRenderer(size, size, false, (c) -> {
- ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c);
- badgeWithDrawable(c, new FastBitmapDrawable(badge), context);
+ result.icon = UiFactory.createFromRenderer(mIconBitmapSize, mIconBitmapSize, false, (c) -> {
+ getShadowGenerator().recreateIcon(unbadgedfinal, c);
+ badgeWithDrawable(c, new FastBitmapDrawable(badge));
});
return result;
}
diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/graphics/ShadowGenerator.java
index 96f2629..5fbf502 100644
--- a/src/com/android/launcher3/graphics/ShadowGenerator.java
+++ b/src/com/android/launcher3/graphics/ShadowGenerator.java
@@ -46,17 +46,13 @@
private static final int AMBIENT_SHADOW_ALPHA = 30;
- private static final Object LOCK = new Object();
- // Singleton object guarded by {@link #LOCK}
- private static ShadowGenerator sShadowGenerator;
-
private final int mIconSize;
private final Paint mBlurPaint;
private final Paint mDrawPaint;
private final BlurMaskFilter mDefaultBlurMaskFilter;
- private ShadowGenerator(Context context) {
+ public ShadowGenerator(Context context) {
mIconSize = LauncherAppState.getIDP(context).iconBitmapSize;
mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
@@ -86,18 +82,6 @@
out.drawBitmap(icon, 0, 0, mDrawPaint);
}
- public static ShadowGenerator getInstance(Context context) {
- // TODO: This currently fails as the system default icon also needs a shadow as it
- // uses adaptive icon.
- // Preconditions.assertNonUiThread();
- synchronized (LOCK) {
- if (sShadowGenerator == null) {
- sShadowGenerator = new ShadowGenerator(context);
- }
- }
- return sShadowGenerator;
- }
-
/**
* 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/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java
index b1d07f1..6378ea1 100644
--- a/src/com/android/launcher3/model/LoaderCursor.java
+++ b/src/com/android/launcher3/model/LoaderCursor.java
@@ -173,7 +173,9 @@
info.iconResource = new ShortcutIconResource();
info.iconResource.packageName = packageName;
info.iconResource.resourceName = resourceName;
- BitmapInfo iconInfo = LauncherIcons.createIconBitmap(info.iconResource, mContext);
+ LauncherIcons li = LauncherIcons.obtain(mContext);
+ BitmapInfo iconInfo = li.createIconBitmap(info.iconResource);
+ li.recycle();
if (iconInfo != null) {
iconInfo.applyTo(info);
return true;
@@ -183,9 +185,8 @@
// Failed to load from resource, try loading from DB.
byte[] data = getBlob(iconIndex);
- try {
- LauncherIcons.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length),
- mContext).applyTo(info);
+ try (LauncherIcons li = LauncherIcons.obtain(mContext)) {
+ li.createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)).applyTo(info);
return true;
} catch (Exception e) {
Log.e(TAG, "Failed to load icon for info " + info, e);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index b13b48a..883c33d 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -215,9 +215,10 @@
public void loadUiResources() {
if (Utilities.ATLEAST_OREO) {
- ClickShadowView.setAdaptiveIconScaleFactor(
- IconNormalizer.getInstance(mApp.getContext()).getScale(
- new AdaptiveIconDrawable(null, null), null, null, null));
+ LauncherIcons li = LauncherIcons.obtain(mApp.getContext());
+ ClickShadowView.setAdaptiveIconScaleFactor(li.getNormalizer()
+ .getScale(new AdaptiveIconDrawable(null, null), null, null, null));
+ li.recycle();
}
}
@@ -476,8 +477,10 @@
? finalInfo.iconBitmap : null;
}
};
- LauncherIcons.createShortcutIcon(pinnedShortcut, context,
+ LauncherIcons li = LauncherIcons.obtain(context);
+ li.createShortcutIcon(pinnedShortcut,
true /* badged */, fallbackIconProvider).applyTo(info);
+ li.recycle();
if (pmHelper.isAppSuspended(
pinnedShortcut.getPackage(), info.user)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 18ae61b..089303e 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -192,8 +192,9 @@
// Update shortcuts which use iconResource.
if ((si.iconResource != null)
&& packageSet.contains(si.iconResource.packageName)) {
- BitmapInfo iconInfo =
- LauncherIcons.createIconBitmap(si.iconResource, context);
+ LauncherIcons li = LauncherIcons.obtain(context);
+ BitmapInfo iconInfo = li.createIconBitmap(si.iconResource);
+ li.recycle();
if (iconInfo != null) {
iconInfo.applyTo(si);
infoUpdated = true;
diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java
index 0b75e2c..59f3d1c 100644
--- a/src/com/android/launcher3/model/ShortcutsChangedTask.java
+++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java
@@ -95,8 +95,10 @@
shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
// If the shortcut is pinned but no longer has an icon in the system,
// keep the current icon instead of reverting to the default icon.
- LauncherIcons.createShortcutIcon(fullDetails, context, true,
- Provider.of(shortcutInfo.iconBitmap)).applyTo(shortcutInfo);
+ LauncherIcons li = LauncherIcons.obtain(context);
+ li.createShortcutIcon(fullDetails, true, Provider.of(shortcutInfo.iconBitmap))
+ .applyTo(shortcutInfo);
+ li.recycle();
updatedShortcutInfos.add(shortcutInfo);
}
}
diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java
index b033405..9521a9e 100644
--- a/src/com/android/launcher3/model/UserLockStateChangedTask.java
+++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java
@@ -94,8 +94,9 @@
si.updateFromDeepShortcutInfo(shortcut, context);
// If the shortcut is pinned but no longer has an icon in the system,
// keep the current icon instead of reverting to the default icon.
- LauncherIcons.createShortcutIcon(shortcut, context, true,
- Provider.of(si.iconBitmap)).applyTo(si);
+ LauncherIcons li = LauncherIcons.obtain(context);
+ li.createShortcutIcon(shortcut, true, Provider.of(si.iconBitmap)).applyTo(si);
+ li.recycle();
} else {
si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER;
}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 4adfb7c..b295bb2 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -148,8 +148,9 @@
final ShortcutInfoCompat shortcut = shortcuts.get(i);
final ShortcutInfo si = new ShortcutInfo(shortcut, launcher);
// Use unbadged icon for the menu.
- LauncherIcons.createShortcutIcon(shortcut, launcher, false /* badged */)
- .applyTo(si);
+ LauncherIcons li = LauncherIcons.obtain(launcher);
+ li.createShortcutIcon(shortcut, false /* badged */).applyTo(si);
+ li.recycle();
si.rank = i;
final DeepShortcutView view = shortcutViews.get(i);
diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java
index c5cf5e2..aa5b785 100644
--- a/src/com/android/launcher3/widget/PendingItemDragHelper.java
+++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java
@@ -113,7 +113,9 @@
} else {
PendingAddShortcutInfo createShortcutInfo = (PendingAddShortcutInfo) mAddInfo;
Drawable icon = createShortcutInfo.activityInfo.getFullResIcon(app.getIconCache());
- preview = LauncherIcons.createScaledBitmapWithoutShadow(icon, launcher, 0);
+ LauncherIcons li = LauncherIcons.obtain(launcher);
+ preview = li.createScaledBitmapWithoutShadow(icon, 0);
+ li.recycle();
scale = ((float) launcher.getDeviceProfile().iconSizePx) / preview.getWidth();
dragOffset = new Point(previewPadding / 2, previewPadding / 2);