[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;
         }