Merge "Making LauncherIcons thread safe" into ub-launcher3-master

This commit is contained in:
Sunny Goyal
2018-01-10 20:03:15 +00:00
committed by Android (Google) Code Review
21 changed files with 276 additions and 391 deletions
-74
View File
@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2015 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.
-->
<com.android.launcher3.folder.Folder xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/round_rect_primary"
android:elevation="5dp"
android:orientation="vertical" >
<com.android.launcher3.folder.FolderPagedView
android:id="@+id/folder_content"
android:clipToPadding="false"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:paddingTop="16dp"
launcher:pageIndicator="@+id/folder_page_indicator" />
<LinearLayout
android:id="@+id/folder_footer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipChildren="false"
android:orientation="horizontal"
android:paddingLeft="12dp"
android:paddingRight="12dp" >
<com.android.launcher3.ExtendedEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="1"
android:background="@android:color/transparent"
android:fontFamily="sans-serif-condensed"
android:gravity="center_horizontal"
android:hint="@string/folder_hint_text"
android:imeOptions="flagNoExtractUi"
android:paddingBottom="@dimen/folder_label_padding_bottom"
android:paddingTop="@dimen/folder_label_padding_top"
android:singleLine="true"
android:textColor="?android:attr/textColorTertiary"
android:includeFontPadding="false"
android:textColorHighlight="?android:attr/colorControlHighlight"
android:textColorHint="?android:attr/textColorHint"
android:textSize="@dimen/folder_label_text_size" />
<com.android.launcher3.pageindicators.PageIndicatorDots
android:id="@+id/folder_page_indicator"
android:layout_gravity="center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
/>
</LinearLayout>
</com.android.launcher3.folder.Folder>
+1 -1
View File
@@ -110,7 +110,7 @@ public class AppInfo extends ItemInfoWithIcon {
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.
@@ -437,9 +437,11 @@ public class AutoInstallsLayout {
}
// 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));
+22 -15
View File
@@ -193,8 +193,10 @@ public class IconCache {
}
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 @@ public class IconCache {
}
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 @@ public class IconCache {
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 @@ public class IconCache {
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 @@ public class IconCache {
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 @@ public class IconCache {
}
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";
@@ -498,7 +498,9 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
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 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
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);
}
+3 -1
View File
@@ -620,7 +620,9 @@ public class LauncherModel extends BroadcastReceiver
@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;
}
});
@@ -469,8 +469,11 @@ public class WidgetPreviewLoader {
}
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);
@@ -137,7 +137,9 @@ public class LauncherAppsCompatVO extends LauncherAppsCompatVL {
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;
@@ -31,8 +31,6 @@ abstract class BaseFlags {
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 @@ abstract class BaseFlags {
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;
@@ -224,8 +224,10 @@ public class DragView extends View {
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
+2 -3
View File
@@ -453,9 +453,8 @@ public class Folder extends AbstractFloatingView implements DragSource, View.OnC
*/
@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) {
@@ -37,10 +37,7 @@ import com.android.launcher3.LauncherAppState;
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 @@ public class IconNormalizer {
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 @@ public class IconNormalizer {
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 @@ public class IconNormalizer {
mMatrix = new Matrix();
mAdaptiveIconScale = SCALE_NOT_INITIALIZED;
mDir = context.getExternalFilesDir(null);
mRandom = new Random();
}
/**
@@ -148,18 +136,9 @@ public class IconNormalizer {
// 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 @@ public class IconNormalizer {
// 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 @@ public class IconNormalizer {
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 @@ public class IconNormalizer {
last = i;
}
}
public static IconNormalizer getInstance(Context context) {
synchronized (LOCK) {
if (sIconNormalizer == null) {
sIconNormalizer = new IconNormalizer(context);
}
}
return sIconNormalizer;
}
}
@@ -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.pm.PackageManager;
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 android.support.annotation.Nullable;
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 @@ import com.android.launcher3.util.Themes;
/**
* 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 @@ public class LauncherIcons {
/**
* 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 @@ public class LauncherIcons {
* 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 @@ public class LauncherIcons {
* 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 @@ public class LauncherIcons {
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;
}
@@ -46,17 +46,13 @@ public class ShadowGenerator {
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 @@ public class ShadowGenerator {
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.
@@ -173,7 +173,9 @@ public class LoaderCursor extends CursorWrapper {
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 @@ public class LoaderCursor extends CursorWrapper {
// 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);
@@ -215,9 +215,10 @@ public class LoaderTask implements Runnable {
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 @@ public class LoaderTask implements Runnable {
? 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;
@@ -192,8 +192,9 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
// 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;
@@ -95,8 +95,10 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask {
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);
}
}
@@ -94,8 +94,9 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask {
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;
}
@@ -148,8 +148,9 @@ public class PopupPopulator {
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);
@@ -113,7 +113,9 @@ public class PendingItemDragHelper extends DragPreviewProvider {
} 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);