[DO NOT MERGE] Limit Icon Drawables to MAX_BITMAP_SIZE
Fixes canvas drawing exceptions caused by unsuported image sizes.
Prevent RemoteActions crashing SystemUi.
Test: Add a RemoteAction for PiP with a very large icon (5k x 5k image)
Test: atest IconTest
Bug: 271544782
Change-Id: Ifa36c2c721dd2fee336423060b600aee8f2ad1ee
(cherry picked from commit 28c379e413ad2619f04f98622f673363a178429b)
diff --git a/core/tests/coretests/res/drawable-nodpi/test_too_big.png b/core/tests/coretests/res/drawable-nodpi/test_too_big.png
new file mode 100644
index 0000000..3754072
--- /dev/null
+++ b/core/tests/coretests/res/drawable-nodpi/test_too_big.png
Binary files differ
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index 75390a2..5d92296 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -20,6 +20,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
+import android.graphics.RecordingCanvas;
import android.graphics.Region;
import android.os.Handler;
import android.os.HandlerThread;
@@ -371,6 +372,90 @@
}
}
+ private int getMaxWidth(int origWidth, int origHeight, int maxNumPixels) {
+ float aspRatio = (float) origWidth / (float) origHeight;
+ int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio);
+ return (int) (newHeight * aspRatio);
+ }
+
+ private int getMaxHeight(int origWidth, int origHeight, int maxNumPixels) {
+ float aspRatio = (float) origWidth / (float) origHeight;
+ return (int) Math.sqrt(maxNumPixels / aspRatio);
+ }
+
+ @SmallTest
+ public void testScaleDownMaxSizeWithBitmap() throws Exception {
+ final int bmpWidth = 13_000;
+ final int bmpHeight = 10_000;
+ final int bmpBpp = 4;
+ final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp;
+ final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels);
+ final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels);
+
+ final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
+ final Icon ic = Icon.createWithBitmap(bm);
+ final Drawable drawable = ic.loadDrawable(mContext);
+
+ assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
+ assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
+ }
+
+ @SmallTest
+ public void testScaleDownMaxSizeWithAdaptiveBitmap() throws Exception {
+ final int bmpWidth = 20_000;
+ final int bmpHeight = 10_000;
+ final int bmpBpp = 4;
+ final int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bmpBpp;
+ final int maxWidth = getMaxWidth(bmpWidth, bmpHeight, maxNumPixels);
+ final int maxHeight = getMaxHeight(bmpWidth, bmpHeight, maxNumPixels);
+
+ final Bitmap bm = Bitmap.createBitmap(bmpWidth, bmpHeight, Bitmap.Config.ARGB_8888);
+ final Icon ic = Icon.createWithAdaptiveBitmap(bm);
+ final AdaptiveIconDrawable adaptiveDrawable = (AdaptiveIconDrawable) ic.loadDrawable(
+ mContext);
+ final Drawable drawable = adaptiveDrawable.getForeground();
+
+ assertThat(drawable.getIntrinsicWidth()).isEqualTo(maxWidth);
+ assertThat(drawable.getIntrinsicHeight()).isEqualTo(maxHeight);
+ }
+
+ @SmallTest
+ public void testScaleDownMaxSizeWithResource() throws Exception {
+ final Icon ic = Icon.createWithResource(getContext(), R.drawable.test_too_big);
+ final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
+
+ assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
+ }
+
+ @SmallTest
+ public void testScaleDownMaxSizeWithFile() throws Exception {
+ final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.test_too_big))
+ .getBitmap();
+ final File dir = getContext().getExternalFilesDir(null);
+ final File file1 = new File(dir, "file1-too-big.png");
+ bit1.compress(Bitmap.CompressFormat.PNG, 100,
+ new FileOutputStream(file1));
+
+ final Icon ic = Icon.createWithFilePath(file1.toString());
+ final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
+
+ assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
+ }
+
+ @SmallTest
+ public void testScaleDownMaxSizeWithData() throws Exception {
+ final int bmpBpp = 4;
+ final Bitmap originalBits = ((BitmapDrawable) getContext().getDrawable(
+ R.drawable.test_too_big)).getBitmap();
+ final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
+ originalBits.getWidth() * originalBits.getHeight() * bmpBpp);
+ originalBits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
+ final byte[] pngdata = ostream.toByteArray();
+ final Icon ic = Icon.createWithData(pngdata, 0, pngdata.length);
+ final BitmapDrawable drawable = (BitmapDrawable) ic.loadDrawable(mContext);
+
+ assertThat(drawable.getBitmap().getByteCount()).isAtMost(RecordingCanvas.MAX_BITMAP_SIZE);
+ }
// ======== utils ========
diff --git a/graphics/java/android/graphics/drawable/Icon.java b/graphics/java/android/graphics/drawable/Icon.java
index a76d74e..708feeb 100644
--- a/graphics/java/android/graphics/drawable/Icon.java
+++ b/graphics/java/android/graphics/drawable/Icon.java
@@ -35,6 +35,7 @@
import android.graphics.BitmapFactory;
import android.graphics.BlendMode;
import android.graphics.PorterDuff;
+import android.graphics.RecordingCanvas;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -70,6 +71,7 @@
public final class Icon implements Parcelable {
private static final String TAG = "Icon";
+ private static final boolean DEBUG = false;
/**
* An icon that was created using {@link Icon#createWithBitmap(Bitmap)}.
@@ -361,15 +363,52 @@
}
/**
+ * Resizes image if size too large for Canvas to draw
+ * @param bitmap Bitmap to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
+ * @return resized bitmap
+ */
+ private Bitmap fixMaxBitmapSize(Bitmap bitmap) {
+ if (bitmap != null && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
+ int bytesPerPixel = bitmap.getRowBytes() / bitmap.getWidth();
+ int maxNumPixels = RecordingCanvas.MAX_BITMAP_SIZE / bytesPerPixel;
+ float aspRatio = (float) bitmap.getWidth() / (float) bitmap.getHeight();
+ int newHeight = (int) Math.sqrt(maxNumPixels / aspRatio);
+ int newWidth = (int) (newHeight * aspRatio);
+
+ if (DEBUG) {
+ Log.d(TAG,
+ "Image size too large: " + bitmap.getByteCount() + ". Resizing bitmap to: "
+ + newWidth + " " + newHeight);
+ }
+
+ return scaleDownIfNecessary(bitmap, newWidth, newHeight);
+ }
+ return bitmap;
+ }
+
+ /**
+ * Resizes BitmapDrawable if size too large for Canvas to draw
+ * @param drawable Drawable to be resized if size > {@link RecordingCanvas.MAX_BITMAP_SIZE}
+ * @return resized Drawable
+ */
+ private Drawable fixMaxBitmapSize(Resources res, Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ Bitmap scaledBmp = fixMaxBitmapSize(((BitmapDrawable) drawable).getBitmap());
+ return new BitmapDrawable(res, scaledBmp);
+ }
+ return drawable;
+ }
+
+ /**
* Do the heavy lifting of loading the drawable, but stop short of applying any tint.
*/
private Drawable loadDrawableInner(Context context) {
switch (mType) {
case TYPE_BITMAP:
- return new BitmapDrawable(context.getResources(), getBitmap());
+ return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap()));
case TYPE_ADAPTIVE_BITMAP:
return new AdaptiveIconDrawable(null,
- new BitmapDrawable(context.getResources(), getBitmap()));
+ new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap())));
case TYPE_RESOURCE:
if (getResources() == null) {
// figure out where to load resources from
@@ -400,7 +439,8 @@
}
}
try {
- return getResources().getDrawable(getResId(), context.getTheme());
+ return fixMaxBitmapSize(getResources(),
+ getResources().getDrawable(getResId(), context.getTheme()));
} catch (RuntimeException e) {
Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
getResId(),
@@ -409,21 +449,21 @@
}
break;
case TYPE_DATA:
- return new BitmapDrawable(context.getResources(),
- BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength())
- );
+ return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(
+ BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(),
+ getDataLength())));
case TYPE_URI:
InputStream is = getUriInputStream(context);
if (is != null) {
return new BitmapDrawable(context.getResources(),
- BitmapFactory.decodeStream(is));
+ fixMaxBitmapSize(BitmapFactory.decodeStream(is)));
}
break;
case TYPE_URI_ADAPTIVE_BITMAP:
is = getUriInputStream(context);
if (is != null) {
return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
- BitmapFactory.decodeStream(is)));
+ fixMaxBitmapSize(BitmapFactory.decodeStream(is))));
}
break;
}