diff options
5 files changed, 80 insertions, 31 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6cc6fb2..ebeaf34 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1917,7 +1917,7 @@ public class Notification implements Parcelable mPeople = new ArrayList<String>(); mColorUtil = context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.L ? - NotificationColorUtil.getInstance() : null; + NotificationColorUtil.getInstance(mContext) : null; } /** @@ -2890,7 +2890,7 @@ public class Notification implements Parcelable } private void processLegacyAction(Action action, RemoteViews button) { - if (!isLegacy() || mColorUtil.isGrayscale(mContext, action.icon)) { + if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, action.icon)) { button.setTextViewCompoundDrawablesRelativeColorFilter(R.id.action0, 0, mContext.getResources().getColor(R.color.notification_action_color_filter), PorterDuff.Mode.MULTIPLY); @@ -2909,7 +2909,7 @@ public class Notification implements Parcelable * Apply any necessary background to smallIcons being used in the largeIcon spot. */ private void processSmallIconAsLarge(int largeIconId, RemoteViews contentView) { - if (!isLegacy() || mColorUtil.isGrayscale(mContext, largeIconId)) { + if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, largeIconId)) { applyLargeIconBackground(contentView); } } @@ -2920,7 +2920,7 @@ public class Notification implements Parcelable */ // TODO: also check bounds, transparency, that sort of thing. private void processLargeLegacyIcon(Bitmap largeIcon, RemoteViews contentView) { - if (isLegacy() && mColorUtil.isGrayscale(largeIcon)) { + if (isLegacy() && mColorUtil.isGrayscaleIcon(largeIcon)) { applyLargeIconBackground(contentView); } else { removeLargeIconBackground(contentView); @@ -2956,7 +2956,7 @@ public class Notification implements Parcelable */ private void processSmallRightIcon(int smallIconDrawableId, RemoteViews contentView) { - if (!isLegacy() || mColorUtil.isGrayscale(mContext, smallIconDrawableId)) { + if (!isLegacy() || mColorUtil.isGrayscaleIcon(mContext, smallIconDrawableId)) { contentView.setDrawableParameters(R.id.right_icon, false, -1, 0xFFFFFFFF, PorterDuff.Mode.SRC_ATOP, -1); diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java index a5ce6e0..c153904 100644 --- a/core/java/com/android/internal/util/ImageUtils.java +++ b/core/java/com/android/internal/util/ImageUtils.java @@ -17,6 +17,10 @@ package com.android.internal.util; import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; /** * Utility class for image analysis and processing. @@ -31,17 +35,49 @@ public class ImageUtils { // Alpha amount for which values below are considered transparent. private static final int ALPHA_TOLERANCE = 50; + // Size of the smaller bitmap we're actually going to scan. + private static final int COMPACT_BITMAP_SIZE = 64; // pixels + private int[] mTempBuffer; + private Bitmap mTempCompactBitmap; + private Canvas mTempCompactBitmapCanvas; + private Paint mTempCompactBitmapPaint; + private final Matrix mTempMatrix = new Matrix(); /** * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect * gray". + * + * Instead of scanning every pixel in the bitmap, we first resize the bitmap to no more than + * COMPACT_BITMAP_SIZE^2 pixels using filtering. The hope is that any non-gray color elements + * will survive the squeezing process, contaminating the result with color. */ public boolean isGrayscale(Bitmap bitmap) { - final int height = bitmap.getHeight(); - final int width = bitmap.getWidth(); - int size = height*width; + int height = bitmap.getHeight(); + int width = bitmap.getWidth(); + + // shrink to a more manageable (yet hopefully no more or less colorful) size + if (height > COMPACT_BITMAP_SIZE || width > COMPACT_BITMAP_SIZE) { + if (mTempCompactBitmap == null) { + mTempCompactBitmap = Bitmap.createBitmap( + COMPACT_BITMAP_SIZE, COMPACT_BITMAP_SIZE, Bitmap.Config.ARGB_8888 + ); + mTempCompactBitmapCanvas = new Canvas(mTempCompactBitmap); + mTempCompactBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mTempCompactBitmapPaint.setFilterBitmap(true); + } + mTempMatrix.reset(); + mTempMatrix.setScale( + (float) COMPACT_BITMAP_SIZE / width, + (float) COMPACT_BITMAP_SIZE / height, + 0, 0); + mTempCompactBitmapCanvas.drawColor(0, PorterDuff.Mode.SRC); // select all, erase + mTempCompactBitmapCanvas.drawBitmap(bitmap, mTempMatrix, mTempCompactBitmapPaint); + bitmap = mTempCompactBitmap; + width = height = COMPACT_BITMAP_SIZE; + } + final int size = height*width; ensureBufferSize(size); bitmap.getPixels(mTempBuffer, 0, width, 0, 0, width, height); for (int i = 0; i < size; i++) { diff --git a/core/java/com/android/internal/util/NotificationColorUtil.java b/core/java/com/android/internal/util/NotificationColorUtil.java index 665055c..3249ea3 100644 --- a/core/java/com/android/internal/util/NotificationColorUtil.java +++ b/core/java/com/android/internal/util/NotificationColorUtil.java @@ -50,23 +50,36 @@ public class NotificationColorUtil { private final WeakHashMap<Bitmap, Pair<Boolean, Integer>> mGrayscaleBitmapCache = new WeakHashMap<Bitmap, Pair<Boolean, Integer>>(); - public static NotificationColorUtil getInstance() { + private final int mGrayscaleIconMaxSize; // @dimen/notification_large_icon_width (64dp) + + public static NotificationColorUtil getInstance(Context context) { synchronized (sLock) { if (sInstance == null) { - sInstance = new NotificationColorUtil(); + sInstance = new NotificationColorUtil(context); } return sInstance; } } + private NotificationColorUtil(Context context) { + mGrayscaleIconMaxSize = context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.notification_large_icon_width); + } + /** - * Checks whether a bitmap is grayscale. Grayscale here means "very close to a perfect - * gray". + * Checks whether a Bitmap is a small grayscale icon. + * Grayscale here means "very close to a perfect gray"; icon means "no larger than 64dp". * * @param bitmap The bitmap to test. - * @return Whether the bitmap is grayscale. + * @return True if the bitmap is grayscale; false if it is color or too large to examine. */ - public boolean isGrayscale(Bitmap bitmap) { + public boolean isGrayscaleIcon(Bitmap bitmap) { + // quick test: reject large bitmaps + if (bitmap.getWidth() > mGrayscaleIconMaxSize + || bitmap.getHeight() > mGrayscaleIconMaxSize) { + return false; + } + synchronized (sLock) { Pair<Boolean, Integer> cached = mGrayscaleBitmapCache.get(bitmap); if (cached != null) { @@ -92,22 +105,22 @@ public class NotificationColorUtil { } /** - * Checks whether a drawable is grayscale. Grayscale here means "very close to a perfect - * gray". + * Checks whether a Drawable is a small grayscale icon. + * Grayscale here means "very close to a perfect gray"; icon means "no larger than 64dp". * * @param d The drawable to test. - * @return Whether the drawable is grayscale. + * @return True if the bitmap is grayscale; false if it is color or too large to examine. */ - public boolean isGrayscale(Drawable d) { + public boolean isGrayscaleIcon(Drawable d) { if (d == null) { return false; } else if (d instanceof BitmapDrawable) { BitmapDrawable bd = (BitmapDrawable) d; - return bd.getBitmap() != null && isGrayscale(bd.getBitmap()); + return bd.getBitmap() != null && isGrayscaleIcon(bd.getBitmap()); } else if (d instanceof AnimationDrawable) { AnimationDrawable ad = (AnimationDrawable) d; int count = ad.getNumberOfFrames(); - return count > 0 && isGrayscale(ad.getFrame(0)); + return count > 0 && isGrayscaleIcon(ad.getFrame(0)); } else if (d instanceof VectorDrawable) { // We just assume you're doing the right thing if using vectors return true; @@ -117,16 +130,16 @@ public class NotificationColorUtil { } /** - * Checks whether a drawable with a resoure id is grayscale. Grayscale here means "very close - * to a perfect gray". + * Checks whether a drawable with a resoure id is a small grayscale icon. + * Grayscale here means "very close to a perfect gray"; icon means "no larger than 64dp". * * @param context The context to load the drawable from. - * @return Whether the drawable is grayscale. + * @return True if the bitmap is grayscale; false if it is color or too large to examine. */ - public boolean isGrayscale(Context context, int drawableResId) { + public boolean isGrayscaleIcon(Context context, int drawableResId) { if (drawableResId != 0) { try { - return isGrayscale(context.getDrawable(drawableResId)); + return isGrayscaleIcon(context.getDrawable(drawableResId)); } catch (Resources.NotFoundException ex) { Log.e(TAG, "Drawable not found: " + drawableResId); return false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index e529c74..2c1d70d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -39,9 +39,6 @@ import android.content.pm.UserInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Color; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; @@ -179,7 +176,7 @@ public abstract class BaseStatusBar extends SystemUI implements // public mode, private notifications, etc private boolean mLockscreenPublicMode = false; private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray(); - private NotificationColorUtil mNotificationColorUtil = NotificationColorUtil.getInstance(); + private NotificationColorUtil mNotificationColorUtil; private UserManager mUserManager; @@ -437,6 +434,8 @@ public abstract class BaseStatusBar extends SystemUI implements mDevicePolicyManager = (DevicePolicyManager)mContext.getSystemService( Context.DEVICE_POLICY_SERVICE); + mNotificationColorUtil = NotificationColorUtil.getInstance(mContext); + mNotificationData = new NotificationData(this); mDreamManager = IDreamManager.Stub.asInterface( @@ -1348,7 +1347,7 @@ public abstract class BaseStatusBar extends SystemUI implements Drawable iconDrawable = StatusBarIconView.getIcon(mContext, ic); icon.setImageDrawable(iconDrawable); - if (mNotificationColorUtil.isGrayscale(iconDrawable)) { + if (mNotificationColorUtil.isGrayscaleIcon(iconDrawable)) { icon.setBackgroundResource( com.android.internal.R.drawable.notification_icon_legacy_bg); int padding = mContext.getResources().getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java index c75bd28..c4c9dac 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java @@ -36,7 +36,7 @@ public class NotificationOverflowIconsView extends IconMerger { private TextView mMoreText; private int mTintColor; private int mIconSize; - private NotificationColorUtil mNotificationColorUtil = new NotificationColorUtil(); + private NotificationColorUtil mNotificationColorUtil; public NotificationOverflowIconsView(Context context, AttributeSet attrs) { super(context, attrs); @@ -45,6 +45,7 @@ public class NotificationOverflowIconsView extends IconMerger { @Override protected void onFinishInflate() { super.onFinishInflate(); + mNotificationColorUtil = NotificationColorUtil.getInstance(getContext()); mTintColor = getResources().getColor(R.color.keyguard_overflow_content_color); mIconSize = getResources().getDimensionPixelSize( com.android.internal.R.dimen.status_bar_icon_size); |