[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.Bitmap;
|
||||||
import android.graphics.Canvas;
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.RecordingCanvas;
|
||||||
import android.graphics.Region;
|
import android.graphics.Region;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
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 ========
|
// ======== utils ========
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import android.graphics.Bitmap;
|
|||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
import android.graphics.BlendMode;
|
import android.graphics.BlendMode;
|
||||||
import android.graphics.PorterDuff;
|
import android.graphics.PorterDuff;
|
||||||
|
import android.graphics.RecordingCanvas;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@@ -70,6 +71,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
public final class Icon implements Parcelable {
|
public final class Icon implements Parcelable {
|
||||||
private static final String TAG = "Icon";
|
private static final String TAG = "Icon";
|
||||||
|
private static final boolean DEBUG = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An icon that was created using {@link Icon#createWithBitmap(Bitmap)}.
|
* An icon that was created using {@link Icon#createWithBitmap(Bitmap)}.
|
||||||
@@ -360,16 +362,53 @@ public final class Icon implements Parcelable {
|
|||||||
return result;
|
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.
|
* Do the heavy lifting of loading the drawable, but stop short of applying any tint.
|
||||||
*/
|
*/
|
||||||
private Drawable loadDrawableInner(Context context) {
|
private Drawable loadDrawableInner(Context context) {
|
||||||
switch (mType) {
|
switch (mType) {
|
||||||
case TYPE_BITMAP:
|
case TYPE_BITMAP:
|
||||||
return new BitmapDrawable(context.getResources(), getBitmap());
|
return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap()));
|
||||||
case TYPE_ADAPTIVE_BITMAP:
|
case TYPE_ADAPTIVE_BITMAP:
|
||||||
return new AdaptiveIconDrawable(null,
|
return new AdaptiveIconDrawable(null,
|
||||||
new BitmapDrawable(context.getResources(), getBitmap()));
|
new BitmapDrawable(context.getResources(), fixMaxBitmapSize(getBitmap())));
|
||||||
case TYPE_RESOURCE:
|
case TYPE_RESOURCE:
|
||||||
if (getResources() == null) {
|
if (getResources() == null) {
|
||||||
// figure out where to load resources from
|
// figure out where to load resources from
|
||||||
@@ -400,7 +439,8 @@ public final class Icon implements Parcelable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return getResources().getDrawable(getResId(), context.getTheme());
|
return fixMaxBitmapSize(getResources(),
|
||||||
|
getResources().getDrawable(getResId(), context.getTheme()));
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
|
Log.e(TAG, String.format("Unable to load resource 0x%08x from pkg=%s",
|
||||||
getResId(),
|
getResId(),
|
||||||
@@ -409,21 +449,21 @@ public final class Icon implements Parcelable {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_DATA:
|
case TYPE_DATA:
|
||||||
return new BitmapDrawable(context.getResources(),
|
return new BitmapDrawable(context.getResources(), fixMaxBitmapSize(
|
||||||
BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(), getDataLength())
|
BitmapFactory.decodeByteArray(getDataBytes(), getDataOffset(),
|
||||||
);
|
getDataLength())));
|
||||||
case TYPE_URI:
|
case TYPE_URI:
|
||||||
InputStream is = getUriInputStream(context);
|
InputStream is = getUriInputStream(context);
|
||||||
if (is != null) {
|
if (is != null) {
|
||||||
return new BitmapDrawable(context.getResources(),
|
return new BitmapDrawable(context.getResources(),
|
||||||
BitmapFactory.decodeStream(is));
|
fixMaxBitmapSize(BitmapFactory.decodeStream(is)));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TYPE_URI_ADAPTIVE_BITMAP:
|
case TYPE_URI_ADAPTIVE_BITMAP:
|
||||||
is = getUriInputStream(context);
|
is = getUriInputStream(context);
|
||||||
if (is != null) {
|
if (is != null) {
|
||||||
return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
|
return new AdaptiveIconDrawable(null, new BitmapDrawable(context.getResources(),
|
||||||
BitmapFactory.decodeStream(is)));
|
fixMaxBitmapSize(BitmapFactory.decodeStream(is))));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user