summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Sandler <dsandler@android.com>2014-09-03 00:16:27 +0200
committerDan Sandler <dsandler@android.com>2014-09-03 23:52:47 +0200
commit05c362d5645367c816069aa138b597b77f317aa4 (patch)
tree9b78e5c2c1f27a4671e4d2b3a7b0452ef1d37b6a
parente21b564168a5383756d386b3ce6c56cd418751d1 (diff)
downloadframeworks_base-05c362d5645367c816069aa138b597b77f317aa4.zip
frameworks_base-05c362d5645367c816069aa138b597b77f317aa4.tar.gz
frameworks_base-05c362d5645367c816069aa138b597b77f317aa4.tar.bz2
Reduce RAM requirements of grayscale icon testing
The isGrayscale family of methods is designed to identify drawables and bitmaps that apps are using in the largeIcon position to pose as small icons in order to get the appropriate background treatment (a solid blue or gray block in KK/JB, or geniune selvedge denim in ICS/HC). We can optimize this search two ways: (1) Reject immediately any largeIcon that is larger than largeIcons should be (64x64dp). We could one day simply reject, or resize, these in the notification manager, but regardless these are not plausible smallIcon subsitutes. This new constraint is commemorated in the new name, isGrayscaleIcon(). (2) Shrink the bitmap even smaller before scanning it slowly in Java. This lets native_drawBitmap do the heavy lifting across the entire bitmap; we need only scan a few pixels. Bug: 16513124 Change-Id: I3a2b79130ed2465a4aedfbb5a556db7f8a7aa132
-rw-r--r--core/java/android/app/Notification.java10
-rw-r--r--core/java/com/android/internal/util/ImageUtils.java42
-rw-r--r--core/java/com/android/internal/util/NotificationColorUtil.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationOverflowIconsView.java3
5 files changed, 80 insertions, 31 deletions
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 800734a..0470f3a 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1916,7 +1916,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;
}
/**
@@ -2889,7 +2889,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);
@@ -2908,7 +2908,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);
}
}
@@ -2919,7 +2919,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);
@@ -2955,7 +2955,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 ce29407..f691411 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -38,9 +38,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;
@@ -177,7 +174,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;
@@ -435,6 +432,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(
@@ -1305,7 +1304,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);