diff --git a/res/values/colors.xml b/res/values/colors.xml index 42fee80928..4abdfd607b 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -32,8 +32,6 @@ #757575 - #1DE9B6 - #1DE9B6 #E0E0E0 #FFFFFF diff --git a/res/values/dimens.xml b/res/values/dimens.xml index 36eb34be80..e10719b5a1 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -212,10 +212,6 @@ 12dp - - 0dp - 3dp - 12dp 16dp diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 162aa0889c..9775955f83 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -29,6 +29,7 @@ import android.database.sqlite.SQLiteDatabase; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; +import android.os.Build.VERSION; import android.os.Bundle; import android.os.Process; import android.text.TextUtils; @@ -438,7 +439,7 @@ public class AutoInstallsLayout { // Auto installs should always support the current platform version. mValues.put(LauncherSettings.Favorites.ICON, Utilities.flattenBitmap( LauncherIcons.createBadgedIconBitmap( - icon, Process.myUserHandle(), mContext, Build.VERSION.SDK_INT))); + icon, Process.myUserHandle(), mContext, VERSION.SDK_INT).icon)); mValues.put(Favorites.ICON_PACKAGE, mIconRes.getResourcePackageName(iconId)); mValues.put(Favorites.ICON_RESOURCE, mIconRes.getResourceName(iconId)); diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index a590504033..46d7227081 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -88,7 +88,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, private BadgeInfo mBadgeInfo; private BadgeRenderer mBadgeRenderer; - private IconPalette mBadgePalette; + private int mBadgeColor; private float mBadgeScale; private boolean mForceHideBadge; private Point mTempSpaceForBadgeOffset = new Point(); @@ -183,7 +183,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, */ public void reset() { mBadgeInfo = null; - mBadgePalette = null; + mBadgeColor = Color.TRANSPARENT; mBadgeScale = 0f; mForceHideBadge = false; } @@ -193,7 +193,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } public void applyFromShortcutInfo(ShortcutInfo info, boolean promiseStateChanged) { - applyIconAndLabel(info.iconBitmap, info); + applyIconAndLabel(info); setTag(info); if (promiseStateChanged || (info.hasPromiseIconUi())) { applyPromiseState(promiseStateChanged); @@ -203,7 +203,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } public void applyFromApplicationInfo(AppInfo info) { - applyIconAndLabel(info.iconBitmap, info); + applyIconAndLabel(info); // We don't need to check the info since it's not a ShortcutInfo super.setTag(info); @@ -219,7 +219,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } public void applyFromPackageItemInfo(PackageItemInfo info) { - applyIconAndLabel(info.iconBitmap, info); + applyIconAndLabel(info); // We don't need to check the info since it's not a ShortcutInfo super.setTag(info); @@ -227,8 +227,10 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, verifyHighRes(); } - private void applyIconAndLabel(Bitmap icon, ItemInfo info) { - FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(icon, info); + private void applyIconAndLabel(ItemInfoWithIcon info) { + FastBitmapDrawable iconDrawable = DrawableFactory.get(getContext()).newIcon(info); + mBadgeColor = IconPalette.getMutedColor(info.iconColor, 0.54f); + iconDrawable.setIsDisabled(info.isDisabled()); setIcon(iconDrawable); setText(info.title); @@ -401,7 +403,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, final int scrollX = getScrollX(); final int scrollY = getScrollY(); canvas.translate(scrollX, scrollY); - mBadgeRenderer.draw(canvas, mBadgePalette, mBadgeInfo, mTempIconBounds, mBadgeScale, + mBadgeRenderer.draw(canvas, mBadgeColor, mTempIconBounds, mBadgeScale, mTempSpaceForBadgeOffset); canvas.translate(-scrollX, -scrollY); } @@ -532,7 +534,7 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, preloadDrawable.setLevel(progressLevel); } else { preloadDrawable = DrawableFactory.get(getContext()) - .newPendingIcon(info.iconBitmap, getContext()); + .newPendingIcon(info, getContext()); preloadDrawable.setLevel(progressLevel); setIcon(preloadDrawable); } @@ -550,10 +552,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, float newBadgeScale = isBadged ? 1f : 0; mBadgeRenderer = mLauncher.getDeviceProfile().mBadgeRenderer; if (wasBadged || isBadged) { - mBadgePalette = IconPalette.getBadgePalette(getResources()); - if (mBadgePalette == null) { - mBadgePalette = ((FastBitmapDrawable) mIcon).getIconPalette(); - } // Animate when a badge is first added or when it is removed. if (animate && (wasBadged ^ isBadged) && isShown()) { ObjectAnimator.ofFloat(this, BADGE_SCALE_PROPERTY, newBadgeScale).start(); @@ -565,10 +563,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, } } - public IconPalette getBadgePalette() { - return mBadgePalette; - } - /** * Sets the icon for this view based on the layout direction. */ diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 7b3da67712..3bfc41c5d4 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -248,7 +248,7 @@ public class DeviceProfile { computeAllAppsButtonSize(context); // This is done last, after iconSizePx is calculated above. - mBadgeRenderer = new BadgeRenderer(context, iconSizePx); + mBadgeRenderer = new BadgeRenderer(iconSizePx); } DeviceProfile getMultiWindowProfile(Context context, Point mwSize) { diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 1272e0ade9..bd19dfa3ca 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -32,7 +32,7 @@ import android.graphics.drawable.Drawable; import android.util.Property; import android.util.SparseArray; -import com.android.launcher3.graphics.IconPalette; +import com.android.launcher3.graphics.BitmapInfo; public class FastBitmapDrawable extends Drawable { @@ -40,19 +40,9 @@ public class FastBitmapDrawable extends Drawable { private static final float DISABLED_DESATURATION = 1f; private static final float DISABLED_BRIGHTNESS = 0.5f; - public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = new TimeInterpolator() { + public static final TimeInterpolator CLICK_FEEDBACK_INTERPOLATOR = (input) -> + (input < 0.05f) ? (input / 0.05f) : ((input < 0.3f) ? 1 : (1 - input) / 0.7f); - @Override - public float getInterpolation(float input) { - if (input < 0.05f) { - return input / 0.05f; - } else if (input < 0.3f){ - return 1; - } else { - return (1 - input) / 0.7f; - } - } - }; public static final int CLICK_FEEDBACK_DURATION = 2000; // Since we don't need 256^2 values for combinations of both the brightness and saturation, we @@ -69,12 +59,11 @@ public class FastBitmapDrawable extends Drawable { protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); private final Bitmap mBitmap; + protected final int mIconColor; private boolean mIsPressed; private boolean mIsDisabled; - private IconPalette mIconPalette; - private static final Property BRIGHTNESS = new Property(Float.TYPE, "brightness") { @Override @@ -99,7 +88,20 @@ public class FastBitmapDrawable extends Drawable { private ObjectAnimator mBrightnessAnimator; public FastBitmapDrawable(Bitmap b) { + this(b, Color.TRANSPARENT); + } + + public FastBitmapDrawable(BitmapInfo info) { + this(info.icon, info.color); + } + + public FastBitmapDrawable(ItemInfoWithIcon info) { + this(info.iconBitmap, info.iconColor); + } + + protected FastBitmapDrawable(Bitmap b, int iconColor) { mBitmap = b; + mIconColor = iconColor; setFilterBitmap(true); } @@ -108,14 +110,6 @@ public class FastBitmapDrawable extends Drawable { canvas.drawBitmap(mBitmap, null, getBounds(), mPaint); } - public IconPalette getIconPalette() { - if (mIconPalette == null) { - mIconPalette = IconPalette.fromDominantColor(Utilities - .findDominantColorByHue(mBitmap, 20), true /* desaturateBackground */); - } - return mIconPalette; - } - @Override public void setColorFilter(ColorFilter cf) { // No op diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index ab853e5a22..baa60b0f2a 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -33,18 +33,21 @@ import android.database.sqlite.SQLiteException; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; -import android.os.Build; +import android.os.Build.VERSION; import android.os.Handler; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; import android.support.annotation.NonNull; +import android.support.v4.graphics.ColorUtils; import android.text.TextUtils; import android.util.Log; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.graphics.ColorExtractor; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.uioverrides.UiFactory; @@ -81,14 +84,13 @@ public class IconCache { @Thunk static final Object ICON_UPDATE_TOKEN = new Object(); - public static class CacheEntry { - public Bitmap icon; + public static class CacheEntry extends BitmapInfo { public CharSequence title = ""; public CharSequence contentDescription = ""; public boolean isLowResIcon; } - private final HashMap mDefaultIcons = new HashMap<>(); + private final HashMap mDefaultIcons = new HashMap<>(); @Thunk final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); private final Context mContext; @@ -190,9 +192,9 @@ public class IconCache { return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); } - protected Bitmap makeDefaultIcon(UserHandle user) { + protected BitmapInfo makeDefaultIcon(UserHandle user) { Drawable unbadged = getFullResDefaultActivityIcon(); - return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, Build.VERSION_CODES.O); + return LauncherIcons.createBadgedIconBitmap(unbadged, user, mContext, VERSION.SDK_INT); } /** @@ -376,16 +378,16 @@ public class IconCache { } if (entry == null) { entry = new CacheEntry(); - entry.icon = LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(), - mContext, app.getApplicationInfo().targetSdkVersion); + LauncherIcons.createBadgedIconBitmap(getFullResIcon(app), app.getUser(), + mContext, app.getApplicationInfo().targetSdkVersion).applyTo(entry); } entry.title = app.getLabel(); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); mCache.put(key, entry); Bitmap lowResIcon = generateLowResIcon(entry.icon); - ContentValues values = newContentValues(entry.icon, lowResIcon, entry.title.toString(), - app.getApplicationInfo().packageName); + ContentValues values = newContentValues(entry.icon, lowResIcon, entry.color, + entry.title.toString(), app.getApplicationInfo().packageName); addIconToDB(values, app.getComponentName(), info, userSerial); } @@ -459,7 +461,7 @@ public class IconCache { // null info means not installed, but if we have a component from the intent then // we should still look in the cache for restored app icons. if (info.getTargetComponent() == null) { - info.iconBitmap = getDefaultIcon(info.user); + getDefaultIcon(info.user).applyTo(info); info.title = ""; info.contentDescription = ""; info.usingLowResIcon = false; @@ -494,11 +496,11 @@ public class IconCache { private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { info.title = Utilities.trim(entry.title); info.contentDescription = entry.contentDescription; - info.iconBitmap = entry.icon == null ? getDefaultIcon(info.user) : entry.icon; info.usingLowResIcon = entry.isLowResIcon; + ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info); } - public synchronized Bitmap getDefaultIcon(UserHandle user) { + public synchronized BitmapInfo getDefaultIcon(UserHandle user) { if (!mDefaultIcons.containsKey(user)) { mDefaultIcons.put(user, makeDefaultIcon(user)); } @@ -506,7 +508,7 @@ public class IconCache { } public boolean isDefaultIcon(Bitmap icon, UserHandle user) { - return mDefaultIcons.get(user) == icon; + return getDefaultIcon(user).icon == icon; } /** @@ -533,9 +535,9 @@ public class IconCache { providerFetchedOnce = true; if (info != null) { - entry.icon = LauncherIcons.createBadgedIconBitmap( + LauncherIcons.createBadgedIconBitmap( getFullResIcon(info), info.getUser(), mContext, - infoProvider.get().getApplicationInfo().targetSdkVersion); + info.getApplicationInfo().targetSdkVersion).applyTo(entry); } else { if (usePackageIcon) { CacheEntry packageEntry = getEntryForPackageLocked( @@ -543,7 +545,7 @@ public class IconCache { if (packageEntry != null) { if (DEBUG) Log.d(TAG, "using package default icon for " + componentName.toShortString()); - entry.icon = packageEntry.icon; + packageEntry.applyTo(entry); entry.title = packageEntry.title; entry.contentDescription = packageEntry.contentDescription; } @@ -551,7 +553,7 @@ public class IconCache { if (entry.icon == null) { if (DEBUG) Log.d(TAG, "using default icon for " + componentName.toShortString()); - entry.icon = getDefaultIcon(user); + getDefaultIcon(user).applyTo(entry); } } } @@ -594,7 +596,7 @@ public class IconCache { entry.title = title; } if (icon != null) { - entry.icon = LauncherIcons.createIconBitmap(icon, mContext); + LauncherIcons.createIconBitmap(icon, mContext).applyTo(entry); } if (!TextUtils.isEmpty(title) && entry.icon != null) { mCache.put(cacheKey, entry); @@ -633,22 +635,23 @@ public class IconCache { // 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 - Bitmap icon = LauncherIcons.createBadgedIconBitmap( + BitmapInfo iconInfo = LauncherIcons.createBadgedIconBitmap( appInfo.loadIcon(mPackageManager), user, mContext, appInfo.targetSdkVersion); if (mInstantAppResolver.isInstantApp(appInfo)) { - LauncherIcons.badgeWithDrawable(icon, + LauncherIcons.badgeWithDrawable(iconInfo.icon, mContext.getDrawable(R.drawable.ic_instant_app_badge), mContext); } - Bitmap lowResIcon = generateLowResIcon(icon); + Bitmap lowResIcon = generateLowResIcon(iconInfo.icon); entry.title = appInfo.loadLabel(mPackageManager); entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - entry.icon = useLowResIcon ? lowResIcon : icon; + entry.icon = useLowResIcon ? lowResIcon : iconInfo.icon; + entry.color = iconInfo.color; entry.isLowResIcon = useLowResIcon; // Add the icon in the DB here, since these do not get written during // package updates. - ContentValues values = - newContentValues(icon, lowResIcon, entry.title.toString(), packageName); + ContentValues values = newContentValues(iconInfo.icon, lowResIcon, entry.color, + entry.title.toString(), packageName); addIconToDB(values, cacheKey.componentName, info, mUserManager.getSerialNumberForUser(user)); @@ -671,14 +674,16 @@ public class IconCache { try { c = mIconDb.query( new String[]{lowRes ? IconDB.COLUMN_ICON_LOW_RES : IconDB.COLUMN_ICON, - IconDB.COLUMN_LABEL}, + IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL}, IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", new String[]{cacheKey.componentName.flattenToString(), Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))}); if (c.moveToNext()) { entry.icon = loadIconNoResize(c, 0, lowRes ? mLowResOptions : mHighResOptions); + // Set the alpha to be 255, so that we never have a wrong color + entry.color = ColorUtils.setAlphaComponent(c.getInt(1), 255); entry.isLowResIcon = lowRes; - entry.title = c.getString(1); + entry.title = c.getString(2); if (entry.title == null) { entry.title = ""; entry.contentDescription = ""; @@ -771,7 +776,7 @@ public class IconCache { } private static final class IconDB extends SQLiteCacheHelper { - private final static int DB_VERSION = 17; + private final static int DB_VERSION = 18; private final static int RELEASE_VERSION = DB_VERSION + (FeatureFlags.LAUNCHER3_DISABLE_ICON_NORMALIZATION ? 0 : 1); @@ -784,6 +789,7 @@ public class IconCache { private final static String COLUMN_VERSION = "version"; private final static String COLUMN_ICON = "icon"; private final static String COLUMN_ICON_LOW_RES = "icon_low_res"; + private final static String COLUMN_ICON_COLOR = "icon_color"; private final static String COLUMN_LABEL = "label"; private final static String COLUMN_SYSTEM_STATE = "system_state"; @@ -802,6 +808,7 @@ public class IconCache { COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_ICON + " BLOB, " + COLUMN_ICON_LOW_RES + " BLOB, " + + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " + COLUMN_LABEL + " TEXT, " + COLUMN_SYSTEM_STATE + " TEXT, " + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " + @@ -809,11 +816,12 @@ public class IconCache { } } - private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, String label, - String packageName) { + private ContentValues newContentValues(Bitmap icon, Bitmap lowResIcon, int iconColor, + String label, String packageName) { ContentValues values = new ContentValues(); values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); values.put(IconDB.COLUMN_ICON_LOW_RES, Utilities.flattenBitmap(lowResIcon)); + values.put(IconDB.COLUMN_ICON_COLOR, iconColor); values.put(IconDB.COLUMN_LABEL, label); values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index df1eec6610..c476421573 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -40,6 +40,7 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.graphics.BitmapInfo; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; @@ -480,7 +481,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { final LauncherAppState app = LauncherAppState.getInstance(mContext); // Set default values until proper values is loaded. appInfo.title = ""; - appInfo.iconBitmap = app.getIconCache().getDefaultIcon(user); + app.getIconCache().getDefaultIcon(user).applyTo(appInfo); final ShortcutInfo si = appInfo.makeShortcut(); if (Looper.myLooper() == LauncherModel.getWorkerLooper()) { app.getIconCache().getTitleAndIcon(si, activityInfo, false /* useLowResIcon */); @@ -497,7 +498,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return Pair.create((ItemInfo) si, (Object) activityInfo); } else if (shortcutInfo != null) { ShortcutInfo si = new ShortcutInfo(shortcutInfo, mContext); - si.iconBitmap = LauncherIcons.createShortcutIcon(shortcutInfo, mContext); + LauncherIcons.createShortcutIcon(shortcutInfo, mContext).applyTo(si); return Pair.create((ItemInfo) si, (Object) shortcutInfo); } else if (providerInfo != null) { LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo @@ -641,18 +642,20 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // users wouldn't get here without intent forwarding anyway. info.user = Process.myUserHandle(); + BitmapInfo iconInfo = null; if (bitmap instanceof Bitmap) { - info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext()); + iconInfo = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext()); } else { Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); if (extra instanceof Intent.ShortcutIconResource) { info.iconResource = (Intent.ShortcutIconResource) extra; - info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, app.getContext()); + iconInfo = LauncherIcons.createIconBitmap(info.iconResource, app.getContext()); } } - if (info.iconBitmap == null) { - info.iconBitmap = app.getIconCache().getDefaultIcon(info.user); + if (iconInfo == null) { + iconInfo = app.getIconCache().getDefaultIcon(info.user); } + iconInfo.applyTo(info); info.title = Utilities.trim(name); info.contentDescription = UserManagerCompat.getInstance(app.getContext()) diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index fea4ddae8e..bf985c38da 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -28,6 +28,11 @@ public abstract class ItemInfoWithIcon extends ItemInfo { */ public Bitmap iconBitmap; + /** + * Dominant color in the {@link #iconBitmap}. + */ + public int iconColor; + /** * Indicates whether we're using a low res icon */ @@ -96,6 +101,7 @@ public abstract class ItemInfoWithIcon extends ItemInfo { protected ItemInfoWithIcon(ItemInfoWithIcon info) { super(info); iconBitmap = info.iconBitmap; + iconColor = info.iconColor; usingLowResIcon = info.usingLowResIcon; runtimeStatusFlags = info.runtimeStatusFlags; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 35bf9e9ee8..ea4b280961 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -620,7 +620,7 @@ public class LauncherModel extends BroadcastReceiver @Override public ShortcutInfo get() { si.updateFromDeepShortcutInfo(info, mApp.getContext()); - si.iconBitmap = LauncherIcons.createShortcutIcon(info, mApp.getContext()); + LauncherIcons.createShortcutIcon(info, mApp.getContext()).applyTo(si); return si; } }); diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index ca235eb010..f8c6ee255d 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -28,7 +28,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.content.res.Resources; import android.graphics.Bitmap; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.Rect; @@ -44,7 +43,6 @@ import android.text.style.TtsSpan; import android.util.DisplayMetrics; import android.util.Log; import android.util.Pair; -import android.util.SparseArray; import android.util.TypedValue; import android.view.View; @@ -285,89 +283,6 @@ public final class Utilities { } } - /** - * This picks a dominant color, looking for high-saturation, high-value, repeated hues. - * @param bitmap The bitmap to scan - * @param samples The approximate max number of samples to use. - */ - public static int findDominantColorByHue(Bitmap bitmap, int samples) { - final int height = bitmap.getHeight(); - final int width = bitmap.getWidth(); - int sampleStride = (int) Math.sqrt((height * width) / samples); - if (sampleStride < 1) { - sampleStride = 1; - } - - // This is an out-param, for getting the hsv values for an rgb - float[] hsv = new float[3]; - - // First get the best hue, by creating a histogram over 360 hue buckets, - // where each pixel contributes a score weighted by saturation, value, and alpha. - float[] hueScoreHistogram = new float[360]; - float highScore = -1; - int bestHue = -1; - - int[] pixels = new int[samples]; - int pixelCount = 0; - - for (int y = 0; y < height; y += sampleStride) { - for (int x = 0; x < width; x += sampleStride) { - int argb = bitmap.getPixel(x, y); - int alpha = 0xFF & (argb >> 24); - if (alpha < 0x80) { - // Drop mostly-transparent pixels. - continue; - } - // Remove the alpha channel. - int rgb = argb | 0xFF000000; - Color.colorToHSV(rgb, hsv); - // Bucket colors by the 360 integer hues. - int hue = (int) hsv[0]; - if (hue < 0 || hue >= hueScoreHistogram.length) { - // Defensively avoid array bounds violations. - continue; - } - if (pixelCount < samples) { - pixels[pixelCount++] = rgb; - } - float score = hsv[1] * hsv[2]; - hueScoreHistogram[hue] += score; - if (hueScoreHistogram[hue] > highScore) { - highScore = hueScoreHistogram[hue]; - bestHue = hue; - } - } - } - - SparseArray rgbScores = new SparseArray<>(); - int bestColor = 0xff000000; - highScore = -1; - // Go back over the RGB colors that match the winning hue, - // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets. - // The highest-scoring RGB color wins. - for (int i = 0; i < pixelCount; i++) { - int rgb = pixels[i]; - Color.colorToHSV(rgb, hsv); - int hue = (int) hsv[0]; - if (hue == bestHue) { - float s = hsv[1]; - float v = hsv[2]; - int bucket = (int) (s * 100) + (int) (v * 10000); - // Score by cumulative saturation * value. - float score = s * v; - Float oldTotal = rgbScores.get(bucket); - float newTotal = oldTotal == null ? score : oldTotal + score; - rgbScores.put(bucket, newTotal); - if (newTotal > highScore) { - highScore = newTotal; - // All the colors in the winning bucket are very similar. Last in wins. - bestColor = rgb; - } - } - } - return bestColor; - } - /* * Finds a system apk which had a broadcast receiver listening to a particular action. * @param action intent action used to find the apk diff --git a/src/com/android/launcher3/badge/BadgeInfo.java b/src/com/android/launcher3/badge/BadgeInfo.java index 08d8ad44e4..170f231b73 100644 --- a/src/com/android/launcher3/badge/BadgeInfo.java +++ b/src/com/android/launcher3/badge/BadgeInfo.java @@ -16,12 +16,6 @@ package com.android.launcher3.badge; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.BitmapShader; -import android.graphics.Canvas; -import android.graphics.Shader; -import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import com.android.launcher3.notification.NotificationInfo; @@ -56,12 +50,6 @@ public class BadgeInfo { /** This will only be initialized if the badge should display the notification icon. */ private NotificationInfo mNotificationInfo; - /** - * When retrieving the notification icon, we draw it into this shader, which can be clipped - * as necessary when drawn in a badge. - */ - private Shader mNotificationIcon; - public BadgeInfo(PackageUserKey packageUserKey) { mPackageUserKey = packageUserKey; mNotificationKeys = new ArrayList<>(); @@ -112,42 +100,12 @@ public class BadgeInfo { public void setNotificationToShow(@Nullable NotificationInfo notificationInfo) { mNotificationInfo = notificationInfo; - mNotificationIcon = null; } public boolean hasNotificationToShow() { return mNotificationInfo != null; } - /** - * Returns a shader to set on a Paint that will draw the notification icon in a badge. - * - * The shader is cached until {@link #setNotificationToShow(NotificationInfo)} is called. - */ - public @Nullable Shader getNotificationIconForBadge(Context context, int badgeColor, - int badgeSize, int badgePadding) { - if (mNotificationInfo == null) { - return null; - } - if (mNotificationIcon == null) { - Drawable icon = mNotificationInfo.getIconForBackground(context, badgeColor) - .getConstantState().newDrawable(); - int iconSize = badgeSize - badgePadding * 2; - icon.setBounds(0, 0, iconSize, iconSize); - Bitmap iconBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(iconBitmap); - canvas.translate(badgePadding, badgePadding); - icon.draw(canvas); - mNotificationIcon = new BitmapShader(iconBitmap, Shader.TileMode.CLAMP, - Shader.TileMode.CLAMP); - } - return mNotificationIcon; - } - - public boolean isIconLarge() { - return mNotificationInfo != null && mNotificationInfo.isIconLarge(); - } - /** * Whether newBadge represents the same PackageUserKey as this badge, and icons with * this badge should be invalidated. So, for instance, if a badge has 3 notifications diff --git a/src/com/android/launcher3/badge/BadgeRenderer.java b/src/com/android/launcher3/badge/BadgeRenderer.java index 6ce334ef35..02ec9f0428 100644 --- a/src/com/android/launcher3/badge/BadgeRenderer.java +++ b/src/com/android/launcher3/badge/BadgeRenderer.java @@ -16,21 +16,17 @@ package com.android.launcher3.badge; -import android.content.Context; -import android.content.res.Resources; +import static android.graphics.Paint.ANTI_ALIAS_FLAG; +import static android.graphics.Paint.FILTER_BITMAP_FLAG; + import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.Shader; -import android.support.annotation.Nullable; import android.util.Log; -import android.util.SparseArray; -import com.android.launcher3.R; -import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.graphics.ShadowGenerator; /** @@ -41,147 +37,58 @@ public class BadgeRenderer { private static final String TAG = "BadgeRenderer"; - private static final boolean DOTS_ONLY = true; - // The badge sizes are defined as percentages of the app icon size. - private static final float SIZE_PERCENTAGE = 0.38f; + private static final float SIZE_PERCENTAGE = 0.23f; // Used to expand the width of the badge for each additional digit. - private static final float CHAR_SIZE_PERCENTAGE = 0.12f; - private static final float TEXT_SIZE_PERCENTAGE = 0.26f; private static final float OFFSET_PERCENTAGE = 0.02f; - private static final float STACK_OFFSET_PERCENTAGE_X = 0.05f; - private static final float STACK_OFFSET_PERCENTAGE_Y = 0.06f; - private static final float DOT_SCALE = 0.6f; - private final Context mContext; private final int mSize; - private final int mCharSize; - private final int mTextHeight; private final int mOffset; - private final int mStackOffsetX; - private final int mStackOffsetY; - private final IconDrawer mLargeIconDrawer; - private final IconDrawer mSmallIconDrawer; - private final Paint mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG - | Paint.FILTER_BITMAP_FLAG); - private final SparseArray mBackgroundsWithShadow; + private final float mCircleRadius; + private final Paint mCirclePaint = new Paint(ANTI_ALIAS_FLAG | FILTER_BITMAP_FLAG); - public BadgeRenderer(Context context, int iconSizePx) { - mContext = context; - Resources res = context.getResources(); + private final Bitmap mBackgroundWithShadow; + private final int mBitmapOffset; + + public BadgeRenderer(int iconSizePx) { mSize = (int) (SIZE_PERCENTAGE * iconSizePx); - mCharSize = (int) (CHAR_SIZE_PERCENTAGE * iconSizePx); mOffset = (int) (OFFSET_PERCENTAGE * iconSizePx); - mStackOffsetX = (int) (STACK_OFFSET_PERCENTAGE_X * iconSizePx); - mStackOffsetY = (int) (STACK_OFFSET_PERCENTAGE_Y * iconSizePx); - mTextPaint.setTextSize(iconSizePx * TEXT_SIZE_PERCENTAGE); - mTextPaint.setTextAlign(Paint.Align.CENTER); - mLargeIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_small_padding)); - mSmallIconDrawer = new IconDrawer(res.getDimensionPixelSize(R.dimen.badge_large_padding)); - // Measure the text height. - Rect tempTextHeight = new Rect(); - mTextPaint.getTextBounds("0", 0, 1, tempTextHeight); - mTextHeight = tempTextHeight.height(); - mBackgroundsWithShadow = new SparseArray<>(3); + ShadowGenerator.Builder builder = new ShadowGenerator.Builder(Color.TRANSPARENT); + mBackgroundWithShadow = builder.setupBlurForSize(mSize).createPill(mSize, mSize); + mCircleRadius = builder.radius; + + mBitmapOffset = -mBackgroundWithShadow.getHeight() / 2; // Same as width. } /** * Draw a circle in the top right corner of the given bounds, and draw * {@link BadgeInfo#getNotificationCount()} on top of the circle. - * @param palette The colors (based on the icon) to use for the badge. - * @param badgeInfo Contains data to draw on the badge. Could be null if we are animating out. + * @param color The color (based on the icon) to use for the badge. * @param iconBounds The bounds of the icon being badged. * @param badgeScale The progress of the animation, from 0 to 1. * @param spaceForOffset How much space is available to offset the badge up and to the right. */ - public void draw(Canvas canvas, IconPalette palette, @Nullable BadgeInfo badgeInfo, - Rect iconBounds, float badgeScale, Point spaceForOffset) { - if (palette == null || iconBounds == null || spaceForOffset == null) { + public void draw( + Canvas canvas, int color, Rect iconBounds, float badgeScale, Point spaceForOffset) { + if (iconBounds == null || spaceForOffset == null) { Log.e(TAG, "Invalid null argument(s) passed in call to draw."); return; } - mTextPaint.setColor(palette.textColor); - IconDrawer iconDrawer = badgeInfo != null && badgeInfo.isIconLarge() - ? mLargeIconDrawer : mSmallIconDrawer; - Shader icon = badgeInfo == null ? null : badgeInfo.getNotificationIconForBadge( - mContext, palette.backgroundColor, mSize, iconDrawer.mPadding); - String notificationCount = badgeInfo == null ? "0" - : String.valueOf(badgeInfo.getNotificationCount()); - int numChars = notificationCount.length(); - int width = DOTS_ONLY ? mSize : mSize + mCharSize * (numChars - 1); - // Lazily load the background with shadow. - Bitmap backgroundWithShadow = mBackgroundsWithShadow.get(numChars); - if (backgroundWithShadow == null) { - backgroundWithShadow = new ShadowGenerator.Builder(Color.WHITE) - .setupBlurForSize(mSize).createPill(width, mSize); - mBackgroundsWithShadow.put(numChars, backgroundWithShadow); - } canvas.save(Canvas.MATRIX_SAVE_FLAG); // We draw the badge relative to its center. - int badgeCenterX = iconBounds.right - width / 2; + int badgeCenterX = iconBounds.right - mSize / 2; int badgeCenterY = iconBounds.top + mSize / 2; - boolean isText = !DOTS_ONLY && badgeInfo != null && badgeInfo.getNotificationCount() != 0; - boolean isIcon = !DOTS_ONLY && icon != null; - boolean isDot = !(isText || isIcon); - if (isDot) { - badgeScale *= DOT_SCALE; - } + int offsetX = Math.min(mOffset, spaceForOffset.x); int offsetY = Math.min(mOffset, spaceForOffset.y); canvas.translate(badgeCenterX + offsetX, badgeCenterY - offsetY); canvas.scale(badgeScale, badgeScale); - // Prepare the background and shadow and possible stacking effect. - mBackgroundPaint.setColorFilter(palette.backgroundColorMatrixFilter); - int backgroundWithShadowSize = backgroundWithShadow.getHeight(); // Same as width. - boolean shouldStack = !isDot && badgeInfo != null - && badgeInfo.getNotificationKeys().size() > 1; - if (shouldStack) { - int offsetDiffX = mStackOffsetX - mOffset; - int offsetDiffY = mStackOffsetY - mOffset; - canvas.translate(offsetDiffX, offsetDiffY); - canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2, - -backgroundWithShadowSize / 2, mBackgroundPaint); - canvas.translate(-offsetDiffX, -offsetDiffY); - } - if (isText) { - canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2, - -backgroundWithShadowSize / 2, mBackgroundPaint); - canvas.drawText(notificationCount, 0, mTextHeight / 2, mTextPaint); - } else if (isIcon) { - canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2, - -backgroundWithShadowSize / 2, mBackgroundPaint); - iconDrawer.drawIcon(icon, canvas); - } else if (isDot) { - mBackgroundPaint.setColorFilter(palette.saturatedBackgroundColorMatrixFilter); - canvas.drawBitmap(backgroundWithShadow, -backgroundWithShadowSize / 2, - -backgroundWithShadowSize / 2, mBackgroundPaint); - } + mCirclePaint.setColor(Color.BLACK); + canvas.drawBitmap(mBackgroundWithShadow, mBitmapOffset, mBitmapOffset, mCirclePaint); + mCirclePaint.setColor(color); + canvas.drawCircle(0, 0, mCircleRadius, mCirclePaint); canvas.restore(); } - - /** Draws the notification icon with padding of a given size. */ - private class IconDrawer { - - private final int mPadding; - private final Bitmap mCircleClipBitmap; - private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | - Paint.FILTER_BITMAP_FLAG); - - public IconDrawer(int padding) { - mPadding = padding; - mCircleClipBitmap = Bitmap.createBitmap(mSize, mSize, Bitmap.Config.ALPHA_8); - Canvas canvas = new Canvas(); - canvas.setBitmap(mCircleClipBitmap); - canvas.drawCircle(mSize / 2, mSize / 2, mSize / 2 - padding, mPaint); - } - - public void drawIcon(Shader icon, Canvas canvas) { - mPaint.setShader(icon); - canvas.drawBitmap(mCircleClipBitmap, -mSize / 2, -mSize / 2, mPaint); - mPaint.setShader(null); - } - } } diff --git a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java index 3214b4672c..5cd90b10e5 100644 --- a/src/com/android/launcher3/compat/LauncherAppsCompatVO.java +++ b/src/com/android/launcher3/compat/LauncherAppsCompatVO.java @@ -137,8 +137,7 @@ 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. - info.iconBitmap = LauncherIcons - .createShortcutIcon(compat, context, false /* badged */); + LauncherIcons.createShortcutIcon(compat, context, false /* badged */).applyTo(info); LauncherAppState.getInstance(context).getModel() .updateAndBindShortcutInfo(info, compat); return info; diff --git a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java index 4cc70d3677..299f09021a 100644 --- a/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java +++ b/src/com/android/launcher3/compat/WallpaperManagerCompatVL.java @@ -18,6 +18,7 @@ package com.android.launcher3.compat; import static android.app.WallpaperManager.FLAG_SYSTEM; import static com.android.launcher3.Utilities.getDevicePrefs; +import static com.android.launcher3.graphics.ColorExtractor.findDominantColorByHue; import android.app.WallpaperInfo; import android.app.WallpaperManager; @@ -257,7 +258,7 @@ public class WallpaperManagerCompatVL extends WallpaperManagerCompat { String value = VERSION_PREFIX + wallpaperId; if (bitmap != null) { - int color = Utilities.findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA); + int color = findDominantColorByHue(bitmap, MAX_WALLPAPER_EXTRACTION_AREA); value += "," + color; } diff --git a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java index ed5cfeb7d6..26e5066bda 100644 --- a/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java +++ b/src/com/android/launcher3/discovery/AppDiscoveryAppInfo.java @@ -18,12 +18,14 @@ package com.android.launcher3.discovery; import android.content.ComponentName; import android.content.Intent; +import android.graphics.Color; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.launcher3.AppInfo; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.graphics.ColorExtractor; public class AppDiscoveryAppInfo extends AppInfo { @@ -41,6 +43,8 @@ public class AppDiscoveryAppInfo extends AppInfo { this.intent = item.isInstantApp ? item.launchIntent : item.installIntent; this.title = item.title; this.iconBitmap = item.bitmap; + this.iconColor = iconBitmap == null ? Color.TRANSPARENT : + ColorExtractor.findDominantColorByHue(item.bitmap); this.usingLowResIcon = false; this.isInstantApp = item.isInstantApp; this.isRecent = item.isRecent; diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 7c89df37c0..11ff88f12d 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -265,7 +265,7 @@ public class DragView extends View { mDrawBitmap = !(dr instanceof FolderAdaptiveIcon); if (info.isDisabled()) { - FastBitmapDrawable d = new FastBitmapDrawable(null); + FastBitmapDrawable d = new FastBitmapDrawable((Bitmap) null); d.setIsDisabled(true); mBaseFilter = (ColorMatrixColorFilter) d.getColorFilter(); } @@ -367,7 +367,8 @@ public class DragView extends View { return new FixedSizeEmptyDrawable(iconSize); } ShortcutInfoCompat si = (ShortcutInfoCompat) obj; - Bitmap badge = LauncherIcons.getShortcutInfoBadge(si, appState.getIconCache()); + Bitmap badge = LauncherIcons.getShortcutInfoBadge(si, appState.getIconCache()) + .iconBitmap; float badgeSize = mLauncher.getResources().getDimension(R.dimen.profile_badge_size); float insetFraction = (iconSize - badgeSize) / iconSize; diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java index 5983029749..4d8d1711b9 100644 --- a/src/com/android/launcher3/folder/FolderIcon.java +++ b/src/com/android/launcher3/folder/FolderIcon.java @@ -63,7 +63,6 @@ import com.android.launcher3.badge.FolderBadgeInfo; import com.android.launcher3.dragndrop.BaseItemDragListener; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.dragndrop.DragView; -import com.android.launcher3.graphics.IconPalette; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.PendingAddShortcutInfo; @@ -499,8 +498,7 @@ public class FolderIcon extends FrameLayout implements FolderListener { // If we are animating to the accepting state, animate the badge out. float badgeScale = Math.max(0, mBadgeScale - mBackground.getScaleProgress()); mTempSpaceForBadgeOffset.set(getWidth() - mTempBounds.right, mTempBounds.top); - IconPalette badgePalette = IconPalette.getFolderBadgePalette(getResources()); - mBadgeRenderer.draw(canvas, badgePalette, mBadgeInfo, mTempBounds, + mBadgeRenderer.draw(canvas, mBackground.getBadgeColor(), mTempBounds, badgeScale, mTempSpaceForBadgeOffset); } } diff --git a/src/com/android/launcher3/folder/PreviewBackground.java b/src/com/android/launcher3/folder/PreviewBackground.java index eba5d984de..285aef8c85 100644 --- a/src/com/android/launcher3/folder/PreviewBackground.java +++ b/src/com/android/launcher3/folder/PreviewBackground.java @@ -200,6 +200,10 @@ public class PreviewBackground { return ColorUtils.setAlphaComponent(mBgColor, alpha); } + public int getBadgeColor() { + return mBgColor; + } + public void drawBackground(Canvas canvas) { mPaint.setStyle(Paint.Style.FILL); mPaint.setColor(getBgColor()); diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/graphics/BitmapInfo.java new file mode 100644 index 0000000000..ab906e2f09 --- /dev/null +++ b/src/com/android/launcher3/graphics/BitmapInfo.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.graphics; + +import android.graphics.Bitmap; + +import com.android.launcher3.ItemInfoWithIcon; + +public class BitmapInfo { + + public Bitmap icon; + public int color; + + public void applyTo(ItemInfoWithIcon info) { + info.iconBitmap = icon; + info.iconColor = color; + } + + public void applyTo(BitmapInfo info) { + info.icon = icon; + info.color = color; + } + + public static BitmapInfo fromBitmap(Bitmap bitmap) { + BitmapInfo info = new BitmapInfo(); + info.icon = bitmap; + info.color = ColorExtractor.findDominantColorByHue(bitmap); + return info; + } +} diff --git a/src/com/android/launcher3/graphics/ColorExtractor.java b/src/com/android/launcher3/graphics/ColorExtractor.java new file mode 100644 index 0000000000..e9d72b7931 --- /dev/null +++ b/src/com/android/launcher3/graphics/ColorExtractor.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.graphics; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.util.SparseArray; + +/** + * Utility class for extracting colors from a bitmap. + */ +public class ColorExtractor { + + public static int findDominantColorByHue(Bitmap bitmap) { + return findDominantColorByHue(bitmap, 20); + } + + /** + * This picks a dominant color, looking for high-saturation, high-value, repeated hues. + * @param bitmap The bitmap to scan + * @param samples The approximate max number of samples to use. + */ + public static int findDominantColorByHue(Bitmap bitmap, int samples) { + final int height = bitmap.getHeight(); + final int width = bitmap.getWidth(); + int sampleStride = (int) Math.sqrt((height * width) / samples); + if (sampleStride < 1) { + sampleStride = 1; + } + + // This is an out-param, for getting the hsv values for an rgb + float[] hsv = new float[3]; + + // First get the best hue, by creating a histogram over 360 hue buckets, + // where each pixel contributes a score weighted by saturation, value, and alpha. + float[] hueScoreHistogram = new float[360]; + float highScore = -1; + int bestHue = -1; + + int[] pixels = new int[samples]; + int pixelCount = 0; + + for (int y = 0; y < height; y += sampleStride) { + for (int x = 0; x < width; x += sampleStride) { + int argb = bitmap.getPixel(x, y); + int alpha = 0xFF & (argb >> 24); + if (alpha < 0x80) { + // Drop mostly-transparent pixels. + continue; + } + // Remove the alpha channel. + int rgb = argb | 0xFF000000; + Color.colorToHSV(rgb, hsv); + // Bucket colors by the 360 integer hues. + int hue = (int) hsv[0]; + if (hue < 0 || hue >= hueScoreHistogram.length) { + // Defensively avoid array bounds violations. + continue; + } + if (pixelCount < samples) { + pixels[pixelCount++] = rgb; + } + float score = hsv[1] * hsv[2]; + hueScoreHistogram[hue] += score; + if (hueScoreHistogram[hue] > highScore) { + highScore = hueScoreHistogram[hue]; + bestHue = hue; + } + } + } + + SparseArray rgbScores = new SparseArray<>(); + int bestColor = 0xff000000; + highScore = -1; + // Go back over the RGB colors that match the winning hue, + // creating a histogram of weighted s*v scores, for up to 100*100 [s,v] buckets. + // The highest-scoring RGB color wins. + for (int i = 0; i < pixelCount; i++) { + int rgb = pixels[i]; + Color.colorToHSV(rgb, hsv); + int hue = (int) hsv[0]; + if (hue == bestHue) { + float s = hsv[1]; + float v = hsv[2]; + int bucket = (int) (s * 100) + (int) (v * 10000); + // Score by cumulative saturation * value. + float score = s * v; + Float oldTotal = rgbScores.get(bucket); + float newTotal = oldTotal == null ? score : oldTotal + score; + rgbScores.put(bucket, newTotal); + if (newTotal > highScore) { + highScore = newTotal; + // All the colors in the winning bucket are very similar. Last in wins. + bestColor = rgb; + } + } + } + return bestColor; + } +} diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index 371479b361..32d9e41455 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -31,7 +31,7 @@ import android.support.annotation.UiThread; import android.util.ArrayMap; import android.util.Log; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.ItemInfo; +import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.allapps.AllAppsBackgroundDrawable; @@ -64,18 +64,18 @@ public class DrawableFactory { /** * Returns a FastBitmapDrawable with the icon. */ - public FastBitmapDrawable newIcon(Bitmap icon, ItemInfo info) { - return new FastBitmapDrawable(icon); + public FastBitmapDrawable newIcon(ItemInfoWithIcon info) { + return new FastBitmapDrawable(info); } /** * Returns a FastBitmapDrawable with the icon. */ - public PreloadIconDrawable newPendingIcon(Bitmap icon, Context context) { + public PreloadIconDrawable newPendingIcon(ItemInfoWithIcon info, Context context) { if (mPreloadProgressPath == null) { mPreloadProgressPath = getPreloadProgressPath(context); } - return new PreloadIconDrawable(icon, mPreloadProgressPath, context); + return new PreloadIconDrawable(info, mPreloadProgressPath, context); } diff --git a/src/com/android/launcher3/graphics/IconPalette.java b/src/com/android/launcher3/graphics/IconPalette.java index 6e01ed5038..9c3b77ece6 100644 --- a/src/com/android/launcher3/graphics/IconPalette.java +++ b/src/com/android/launcher3/graphics/IconPalette.java @@ -18,12 +18,7 @@ package com.android.launcher3.graphics; import android.app.Notification; import android.content.Context; -import android.content.res.Resources; import android.graphics.Color; -import android.graphics.ColorMatrix; -import android.graphics.ColorMatrixColorFilter; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v4.graphics.ColorUtils; import android.util.Log; @@ -41,37 +36,10 @@ public class IconPalette { private static final float MIN_PRELOAD_COLOR_SATURATION = 0.2f; private static final float MIN_PRELOAD_COLOR_LIGHTNESS = 0.6f; - private static IconPalette sBadgePalette; - private static IconPalette sFolderBadgePalette; - - public final int dominantColor; - public final int backgroundColor; - public final ColorMatrixColorFilter backgroundColorMatrixFilter; - public final ColorMatrixColorFilter saturatedBackgroundColorMatrixFilter; - public final int textColor; - public final int secondaryColor; - - private IconPalette(int color, boolean desaturateBackground) { - dominantColor = color; - backgroundColor = desaturateBackground ? getMutedColor(dominantColor, 0.87f) : dominantColor; - ColorMatrix backgroundColorMatrix = new ColorMatrix(); - Themes.setColorScaleOnMatrix(backgroundColor, backgroundColorMatrix); - backgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix); - if (!desaturateBackground) { - saturatedBackgroundColorMatrixFilter = backgroundColorMatrixFilter; - } else { - // Get slightly more saturated background color. - Themes.setColorScaleOnMatrix(getMutedColor(dominantColor, 0.54f), backgroundColorMatrix); - saturatedBackgroundColorMatrixFilter = new ColorMatrixColorFilter(backgroundColorMatrix); - } - textColor = getTextColorForBackground(backgroundColor); - secondaryColor = getLowContrastColor(backgroundColor); - } - /** * Returns a color suitable for the progress bar color of preload icon. */ - public int getPreloadProgressColor(Context context) { + public static int getPreloadProgressColor(Context context, int dominantColor) { int result = dominantColor; // Make sure that the dominant color has enough saturation to be visible properly. @@ -86,37 +54,6 @@ public class IconPalette { return result; } - public static IconPalette fromDominantColor(int dominantColor, boolean desaturateBackground) { - return new IconPalette(dominantColor, desaturateBackground); - } - - /** - * Returns an IconPalette based on the badge_color in colors.xml. - * If that color is Color.TRANSPARENT, then returns null instead. - */ - public static @Nullable IconPalette getBadgePalette(Resources resources) { - int badgeColor = resources.getColor(R.color.badge_color); - if (badgeColor == Color.TRANSPARENT) { - // Colors will be extracted per app icon, so a static palette won't work. - return null; - } - if (sBadgePalette == null) { - sBadgePalette = fromDominantColor(badgeColor, false); - } - return sBadgePalette; - } - - /** - * Returns an IconPalette based on the folder_badge_color in colors.xml. - */ - public static @NonNull IconPalette getFolderBadgePalette(Resources resources) { - if (sFolderBadgePalette == null) { - int badgeColor = resources.getColor(R.color.folder_badge_color); - sFolderBadgePalette = fromDominantColor(badgeColor, false); - } - return sFolderBadgePalette; - } - /** * Resolves a color such that it has enough contrast to be used as the * color of an icon or text on the given background color. @@ -208,30 +145,8 @@ public class IconPalette { return ColorUtils.LABToColor(low, a, b); } - private static int getMutedColor(int color, float whiteScrimAlpha) { + public static int getMutedColor(int color, float whiteScrimAlpha) { int whiteScrim = ColorUtils.setAlphaComponent(Color.WHITE, (int) (255 * whiteScrimAlpha)); return ColorUtils.compositeColors(whiteScrim, color); } - - private static int getTextColorForBackground(int backgroundColor) { - return getLighterOrDarkerVersionOfColor(backgroundColor, 4.5f); - } - - private static int getLowContrastColor(int color) { - return getLighterOrDarkerVersionOfColor(color, 1.5f); - } - - private static int getLighterOrDarkerVersionOfColor(int color, float contrastRatio) { - int whiteMinAlpha = ColorUtils.calculateMinimumAlpha(Color.WHITE, color, contrastRatio); - int blackMinAlpha = ColorUtils.calculateMinimumAlpha(Color.BLACK, color, contrastRatio); - int translucentWhiteOrBlack; - if (whiteMinAlpha >= 0) { - translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.WHITE, whiteMinAlpha); - } else if (blackMinAlpha >= 0) { - translucentWhiteOrBlack = ColorUtils.setAlphaComponent(Color.BLACK, blackMinAlpha); - } else { - translucentWhiteOrBlack = Color.WHITE; - } - return ColorUtils.compositeColors(translucentWhiteOrBlack, color); - } } diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index 8c4738cd2b..fdb631397f 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -21,7 +21,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.Intent.ShortcutIconResource; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.graphics.Bitmap; @@ -42,6 +41,7 @@ import android.support.annotation.Nullable; import com.android.launcher3.AppInfo; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.IconCache; +import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppState; import com.android.launcher3.R; import com.android.launcher3.Utilities; @@ -51,6 +51,7 @@ import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.uioverrides.UiFactory; import com.android.launcher3.util.Provider; +import com.android.launcher3.util.Themes; /** * Helper methods for generating various launcher icons @@ -69,7 +70,7 @@ public class LauncherIcons { * Returns a bitmap suitable for the all apps view. If the package or the resource do not * exist, it returns null. */ - public static Bitmap createIconBitmap(ShortcutIconResource iconRes, Context context) { + public static BitmapInfo createIconBitmap(ShortcutIconResource iconRes, Context context) { PackageManager packageManager = context.getPackageManager(); // the resource try { @@ -92,12 +93,13 @@ public class LauncherIcons { /** * Returns a bitmap which is of the appropriate size to be displayed as an icon */ - public static Bitmap createIconBitmap(Bitmap icon, Context context) { + public static BitmapInfo createIconBitmap(Bitmap icon, Context context) { final int iconBitmapSize = LauncherAppState.getIDP(context).iconBitmapSize; if (iconBitmapSize == icon.getWidth() && iconBitmapSize == icon.getHeight()) { - return icon; + return BitmapInfo.fromBitmap(icon); } - return createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f); + return BitmapInfo.fromBitmap( + createIconBitmap(new BitmapDrawable(context.getResources(), icon), context, 1f)); } /** @@ -105,7 +107,7 @@ public class LauncherIcons { * view or workspace. The icon is badged for {@param user}. * The bitmap is also visually normalized with other icons. */ - public static Bitmap createBadgedIconBitmap( + public static BitmapInfo createBadgedIconBitmap( Drawable icon, UserHandle user, Context context, int iconAppTargetSdk) { IconNormalizer normalizer; @@ -141,18 +143,20 @@ public class LauncherIcons { } } + final Bitmap result; if (user != null && !Process.myUserHandle().equals(user)) { BitmapDrawable drawable = new FixedSizeBitmapDrawable(bitmap); Drawable badged = context.getPackageManager().getUserBadgedIcon( drawable, user); if (badged instanceof BitmapDrawable) { - return ((BitmapDrawable) badged).getBitmap(); + result = ((BitmapDrawable) badged).getBitmap(); } else { - return createIconBitmap(badged, context, 1f); + result = createIconBitmap(badged, context, 1f); } } else { - return bitmap; + result = bitmap; } + return BitmapInfo.fromBitmap(result); } /** @@ -302,29 +306,23 @@ public class LauncherIcons { return drawable; } - public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) { + public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context) { return createShortcutIcon(shortcutInfo, context, true /* badged */); } - public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, + public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged) { return createShortcutIcon(shortcutInfo, context, badged, null); } - public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, - final Bitmap fallbackIcon) { - // 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. - return createShortcutIcon(shortcutInfo, context, true, Provider.of(fallbackIcon)); - } - - public static Bitmap createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, + public static BitmapInfo createShortcutIcon(ShortcutInfoCompat shortcutInfo, Context context, boolean badged, @Nullable Provider fallbackIconProvider) { LauncherAppState app = LauncherAppState.getInstance(context); Drawable unbadgedDrawable = DeepShortcutManager.getInstance(context) .getShortcutIconDrawable(shortcutInfo, app.getInvariantDeviceProfile().fillResIconDpi); IconCache cache = app.getIconCache(); + Bitmap unbadgedBitmap = null; if (unbadgedDrawable != null) { unbadgedBitmap = LauncherIcons.createScaledBitmapWithoutShadow( @@ -334,27 +332,32 @@ public class LauncherIcons { unbadgedBitmap = fallbackIconProvider.get(); } if (unbadgedBitmap == null) { - unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()); + unbadgedBitmap = cache.getDefaultIcon(Process.myUserHandle()).icon; } } + BitmapInfo result = new BitmapInfo(); if (!badged) { - return unbadgedBitmap; + result.color = Themes.getColorAccent(context); + result.icon = unbadgedBitmap; + return result; } int size = app.getInvariantDeviceProfile().iconBitmapSize; final Bitmap unbadgedfinal = unbadgedBitmap; - final Bitmap badge = getShortcutInfoBadge(shortcutInfo, cache); + final ItemInfoWithIcon badge = getShortcutInfoBadge(shortcutInfo, cache); - return UiFactory.createFromRenderer(size, size, false, (c) -> { + result.color = badge.iconColor; + result.icon = UiFactory.createFromRenderer(size, size, false, (c) -> { ShadowGenerator.getInstance(context).recreateIcon(unbadgedfinal, c); badgeWithDrawable(c, new FastBitmapDrawable(badge), context); }); + return result; } - public static Bitmap getShortcutInfoBadge(ShortcutInfoCompat shortcutInfo, IconCache cache) { - final Bitmap badgeBitmap; + public static ItemInfoWithIcon getShortcutInfoBadge( + ShortcutInfoCompat shortcutInfo, IconCache cache) { ComponentName cn = shortcutInfo.getActivity(); if (cn != null) { // Get the app info for the source activity. @@ -365,13 +368,12 @@ public class LauncherIcons { .addCategory(Intent.CATEGORY_LAUNCHER) .setComponent(cn); cache.getTitleAndIcon(appInfo, false); - badgeBitmap = appInfo.iconBitmap; + return appInfo; } else { PackageItemInfo pkgInfo = new PackageItemInfo(shortcutInfo.getPackage()); cache.getTitleAndIconForApp(pkgInfo, false); - badgeBitmap = pkgInfo.iconBitmap; + return pkgInfo; } - return badgeBitmap; } /** diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java index 6d486eed72..a40b6df93b 100644 --- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java +++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java @@ -32,6 +32,7 @@ import android.util.Property; import android.util.SparseArray; import com.android.launcher3.FastBitmapDrawable; +import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.anim.Interpolators; import java.lang.ref.WeakReference; @@ -86,7 +87,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable { private final Paint mProgressPaint; private Bitmap mShadowBitmap; - private int mIndicatorColor = 0; + private final int mIndicatorColor; private int mTrackAlpha; private float mTrackLength; @@ -103,8 +104,8 @@ public class PreloadIconDrawable extends FastBitmapDrawable { /** * @param progressPath fixed path in the bounds [0, 0, 100, 100] representing a progress bar. */ - public PreloadIconDrawable(Bitmap b, Path progressPath, Context context) { - super(b); + public PreloadIconDrawable(ItemInfoWithIcon info, Path progressPath, Context context) { + super(info); mContext = context; mProgressPath = progressPath; mScaledTrackPath = new Path(); @@ -113,6 +114,7 @@ public class PreloadIconDrawable extends FastBitmapDrawable { mProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG); mProgressPaint.setStyle(Paint.Style.STROKE); mProgressPaint.setStrokeCap(Paint.Cap.ROUND); + mIndicatorColor = IconPalette.getPreloadProgressColor(context, mIconColor); setInternalProgress(0); } @@ -266,9 +268,6 @@ public class PreloadIconDrawable extends FastBitmapDrawable { mScaledTrackPath.reset(); mTrackAlpha = MAX_PAINT_ALPHA; setIsDisabled(true); - } else if (mIndicatorColor == 0) { - // Update the indicator color - mIndicatorColor = getIconPalette().getPreloadProgressColor(mContext); } if (progress < 1 && progress > 0) { diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index ccef9b7c7d..b1d07f190b 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -44,6 +44,7 @@ import com.android.launcher3.Workspace; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.BitmapInfo; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ContentWriter; @@ -151,10 +152,9 @@ public class LoaderCursor extends CursorWrapper { info.user = user; info.itemType = itemType; info.title = getTitle(); - info.iconBitmap = loadIcon(info); // the fallback icon - if (info.iconBitmap == null) { - info.iconBitmap = mIconCache.getDefaultIcon(info.user); + if (!loadIcon(info)) { + mIconCache.getDefaultIcon(info.user).applyTo(info); } // TODO: If there's an explicit component and we can't install that, delete it. @@ -165,8 +165,7 @@ public class LoaderCursor extends CursorWrapper { /** * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource. */ - protected Bitmap loadIcon(ShortcutInfo info) { - Bitmap icon = null; + protected boolean loadIcon(ShortcutInfo info) { if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { String packageName = getString(iconPackageIndex); String resourceName = getString(iconResourceIndex); @@ -174,24 +173,24 @@ public class LoaderCursor extends CursorWrapper { info.iconResource = new ShortcutIconResource(); info.iconResource.packageName = packageName; info.iconResource.resourceName = resourceName; - icon = LauncherIcons.createIconBitmap(info.iconResource, mContext); + BitmapInfo iconInfo = LauncherIcons.createIconBitmap(info.iconResource, mContext); + if (iconInfo != null) { + iconInfo.applyTo(info); + return true; + } } } - if (icon == null) { - // Failed to load from resource, try loading from DB. - byte[] data = getBlob(iconIndex); - try { - icon = LauncherIcons.createIconBitmap( - BitmapFactory.decodeByteArray(data, 0, data.length), mContext); - } catch (Exception e) { - Log.e(TAG, "Failed to load icon for info " + info, e); - return null; - } + + // 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); + return true; + } catch (Exception e) { + Log.e(TAG, "Failed to load icon for info " + info, e); + return false; } - if (icon == null) { - Log.e(TAG, "Failed to load icon for info " + info); - } - return icon; } /** @@ -211,9 +210,8 @@ public class LoaderCursor extends CursorWrapper { info.user = user; info.intent = intent; - info.iconBitmap = loadIcon(info); // the fallback icon - if (info.iconBitmap == null) { + if (!loadIcon(info)) { mIconCache.getTitleAndIcon(info, false /* useLowResIcon */); } @@ -269,8 +267,7 @@ public class LoaderCursor extends CursorWrapper { mIconCache.getTitleAndIcon(info, lai, useLowResIcon); if (mIconCache.isDefaultIcon(info.iconBitmap, user)) { - Bitmap icon = loadIcon(info); - info.iconBitmap = icon != null ? icon : info.iconBitmap; + loadIcon(info); } if (lai != null) { diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 310416f0b8..b13b48a25b 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -472,12 +472,12 @@ public class LoaderTask implements Runnable { public Bitmap get() { // If the pinned deep shortcut is no longer published, // use the last saved icon instead of the default. - return c.loadIcon(finalInfo); + return c.loadIcon(finalInfo) + ? finalInfo.iconBitmap : null; } }; - info.iconBitmap = LauncherIcons - .createShortcutIcon(pinnedShortcut, context, - true /* badged */, fallbackIconProvider); + LauncherIcons.createShortcutIcon(pinnedShortcut, context, + true /* badged */, fallbackIconProvider).applyTo(info); 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 470dadf724..18ae61b1fa 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -40,6 +40,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.BitmapInfo; import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.ItemInfoMatcher; @@ -191,9 +192,10 @@ public class PackageUpdatedTask extends BaseModelUpdateTask { // Update shortcuts which use iconResource. if ((si.iconResource != null) && packageSet.contains(si.iconResource.packageName)) { - Bitmap icon = LauncherIcons.createIconBitmap(si.iconResource, context); - if (icon != null) { - si.iconBitmap = icon; + BitmapInfo iconInfo = + LauncherIcons.createIconBitmap(si.iconResource, context); + 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 c1f33a6b2c..0b75e2c88c 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -29,6 +29,7 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.MultiHashMap; +import com.android.launcher3.util.Provider; import java.util.ArrayList; import java.util.HashSet; @@ -92,8 +93,10 @@ public class ShortcutsChangedTask extends BaseModelUpdateTask { } for (final ShortcutInfo shortcutInfo : shortcutInfos) { shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context); - shortcutInfo.iconBitmap = LauncherIcons.createShortcutIcon(fullDetails, context, - shortcutInfo.iconBitmap); + // 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); updatedShortcutInfos.add(shortcutInfo); } } diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 2e9ac72243..b033405e8b 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -32,6 +32,7 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ItemInfoMatcher; +import com.android.launcher3.util.Provider; import java.util.ArrayList; import java.util.HashMap; @@ -91,8 +92,10 @@ public class UserLockStateChangedTask extends BaseModelUpdateTask { } si.runtimeStatusFlags &= ~FLAG_DISABLED_LOCKED_USER; si.updateFromDeepShortcutInfo(shortcut, context); - si.iconBitmap = LauncherIcons.createShortcutIcon(shortcut, context, - si.iconBitmap); + // 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); } else { si.runtimeStatusFlags |= FLAG_DISABLED_LOCKED_USER; } diff --git a/src/com/android/launcher3/notification/NotificationInfo.java b/src/com/android/launcher3/notification/NotificationInfo.java index 120de0464d..6918935794 100644 --- a/src/com/android/launcher3/notification/NotificationInfo.java +++ b/src/com/android/launcher3/notification/NotificationInfo.java @@ -83,7 +83,7 @@ public class NotificationInfo implements View.OnClickListener { if (mIconDrawable == null) { mIconDrawable = new BitmapDrawable(context.getResources(), LauncherAppState .getInstance(context).getIconCache() - .getDefaultIcon(statusBarNotification.getUser())); + .getDefaultIcon(statusBarNotification.getUser()).icon); mBadgeIcon = Notification.BADGE_ICON_NONE; } intent = notification.contentIntent; diff --git a/src/com/android/launcher3/notification/NotificationItemView.java b/src/com/android/launcher3/notification/NotificationItemView.java index 5bbd19c858..2fefa85ea3 100644 --- a/src/com/android/launcher3/notification/NotificationItemView.java +++ b/src/com/android/launcher3/notification/NotificationItemView.java @@ -18,6 +18,7 @@ package com.android.launcher3.notification; import android.app.Notification; import android.content.Context; +import android.graphics.Color; import android.graphics.Rect; import android.support.annotation.Nullable; import android.view.MotionEvent; @@ -114,12 +115,12 @@ public class NotificationItemView { } } - public void updateHeader(int notificationCount, @Nullable IconPalette palette) { + public void updateHeader(int notificationCount, int iconColor) { mHeaderCount.setText(notificationCount <= 1 ? "" : String.valueOf(notificationCount)); - if (palette != null) { + if (Color.alpha(iconColor) > 0) { if (mNotificationHeaderTextColor == Notification.COLOR_DEFAULT) { mNotificationHeaderTextColor = - IconPalette.resolveContrastColor(mContext, palette.dominantColor, + IconPalette.resolveContrastColor(mContext, iconColor, Themes.getAttrColor(mContext, R.attr.popupColorPrimary)); } mHeaderText.setTextColor(mNotificationHeaderTextColor); diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java index 6481183e19..cedf2917bb 100644 --- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java +++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java @@ -54,6 +54,7 @@ import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; import com.android.launcher3.DropTarget.DragObject; import com.android.launcher3.ItemInfo; +import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherAnimUtils; import com.android.launcher3.LauncherModel; @@ -742,11 +743,11 @@ public class PopupContainerWithArrow extends AbstractFloatingView implements Dra } private void updateNotificationHeader() { - ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag(); + ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag(); BadgeInfo badgeInfo = mLauncher.getPopupDataProvider().getBadgeInfoForItem(itemInfo); if (mNotificationItemView != null && badgeInfo != null) { - IconPalette palette = mOriginalIcon.getBadgePalette(); - mNotificationItemView.updateHeader(badgeInfo.getNotificationCount(), palette); + mNotificationItemView.updateHeader( + badgeInfo.getNotificationCount(), itemInfo.iconColor); } } diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index 6c83d126fc..4adfb7c0e6 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -148,8 +148,8 @@ public class PopupPopulator { final ShortcutInfoCompat shortcut = shortcuts.get(i); final ShortcutInfo si = new ShortcutInfo(shortcut, launcher); // Use unbadged icon for the menu. - si.iconBitmap = LauncherIcons.createShortcutIcon( - shortcut, launcher, false /* badged */); + LauncherIcons.createShortcutIcon(shortcut, launcher, false /* badged */) + .applyTo(si); si.rank = i; final DeepShortcutView view = shortcutViews.get(i); diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java index 24bcebb2e1..6970833366 100644 --- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java @@ -58,8 +58,6 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView private final int mStartState; private final boolean mDisabledForSafeMode; - private Bitmap mIcon; - private Drawable mCenterDrawable; private Drawable mSettingIconDrawable; @@ -129,53 +127,44 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView @Override public void reapplyItemInfo(ItemInfoWithIcon info) { - Bitmap icon = info.iconBitmap; - if (mIcon == icon) { - return; - } - mIcon = icon; if (mCenterDrawable != null) { mCenterDrawable.setCallback(null); mCenterDrawable = null; } - if (mIcon != null) { + if (info.iconBitmap != null) { // The view displays three modes, // 1) App icon in the center // 2) Preload icon in the center // 3) Setup icon in the center and app icon in the top right corner. DrawableFactory drawableFactory = DrawableFactory.get(getContext()); if (mDisabledForSafeMode) { - FastBitmapDrawable disabledIcon = drawableFactory.newIcon(mIcon, mInfo); + FastBitmapDrawable disabledIcon = drawableFactory.newIcon(info); disabledIcon.setIsDisabled(true); mCenterDrawable = disabledIcon; mSettingIconDrawable = null; } else if (isReadyForClickSetup()) { - mCenterDrawable = drawableFactory.newIcon(mIcon, mInfo); + mCenterDrawable = drawableFactory.newIcon(info); mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate(); - - updateSettingColor(); + updateSettingColor(info.iconColor); } else { mCenterDrawable = DrawableFactory.get(getContext()) - .newPendingIcon(mIcon, getContext()); - mCenterDrawable.setCallback(this); + .newPendingIcon(info, getContext()); mSettingIconDrawable = null; applyState(); } + mCenterDrawable.setCallback(this); mDrawableSizeChanged = true; } invalidate(); } - private void updateSettingColor() { - int color = Utilities.findDominantColorByHue(mIcon, 20); + private void updateSettingColor(int dominantColor) { // Make the dominant color bright. float[] hsv = new float[3]; - Color.colorToHSV(color, hsv); + Color.colorToHSV(dominantColor, hsv); hsv[1] = Math.min(hsv[1], MIN_SATUNATION); hsv[2] = 1; - color = Color.HSVToColor(hsv); - - mSettingIconDrawable.setColorFilter(color, PorterDuff.Mode.SRC_IN); + mSettingIconDrawable.setColorFilter(Color.HSVToColor(hsv), PorterDuff.Mode.SRC_IN); } @Override diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index bbb6772d3f..cf90afd4b1 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -9,12 +9,12 @@ import android.content.pm.LauncherActivityInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; +import android.graphics.Color; import android.os.Process; import android.os.UserHandle; import android.support.annotation.NonNull; import android.support.test.InstrumentationRegistry; import android.support.test.rule.provider.ProviderTestRule; -import android.support.test.runner.AndroidJUnit4; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppFilter; @@ -27,6 +27,7 @@ import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherProvider; +import com.android.launcher3.graphics.BitmapInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Provider; import com.android.launcher3.util.TestLauncherProvider; @@ -208,7 +209,7 @@ public class BaseModelUpdateTaskTestCase { CacheEntry entry = mCache.get(new ComponentKey(componentName, user)); if (entry == null) { entry = new CacheEntry(); - entry.icon = getDefaultIcon(user); + getDefaultIcon(user).applyTo(entry); } return entry; } @@ -216,6 +217,7 @@ public class BaseModelUpdateTaskTestCase { public void addCache(ComponentName key, String title) { CacheEntry entry = new CacheEntry(); entry.icon = newIcon(); + entry.color = Color.RED; entry.title = title; mCache.put(new ComponentKey(key, Process.myUserHandle()), entry); } @@ -225,8 +227,8 @@ public class BaseModelUpdateTaskTestCase { } @Override - protected Bitmap makeDefaultIcon(UserHandle user) { - return newIcon(); + protected BitmapInfo makeDefaultIcon(UserHandle user) { + return BitmapInfo.fromBitmap(newIcon()); } } } diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 173c556218..dfefa3140a 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -17,6 +17,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.graphics.BitmapInfo; import org.junit.Before; import org.junit.Test; @@ -138,7 +139,8 @@ public class LoaderCursorTest { assertTrue(mLoaderCursor.moveToNext()); Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); - when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))).thenReturn(icon); + when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))) + .thenReturn(BitmapInfo.fromBitmap(icon)); ShortcutInfo info = mLoaderCursor.loadSimpleShortcut(); assertEquals(icon, info.iconBitmap); assertEquals("my-shortcut", info.title);