[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 28c379e413)
This commit is contained in:
committed by
Iavor-Valentin Iftime
parent
f785873128
commit
640966b46d
BIN
core/tests/coretests/res/drawable-nodpi/test_too_big.png
Normal file
BIN
core/tests/coretests/res/drawable-nodpi/test_too_big.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 151 KiB |
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
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 @@ public class IconTest extends AndroidTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
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 ========
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import android.graphics.Bitmap;
|
||||
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 @@ import java.util.Objects;
|
||||
|
||||
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)}.
|
||||
@@ -360,16 +362,53 @@ public final class Icon implements Parcelable {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public final class Icon implements Parcelable {
|
||||
}
|
||||
}
|
||||
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 @@ public final class Icon implements Parcelable {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user