Merge "Fix handling of negative size request for Bitmaps in LocalImageResolver" into tm-dev am: 0c36369480

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17504348

Change-Id: I98037ef693c42d17aea6cc36d0c4aef84b26f439
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Jernej Virag
2022-03-31 11:45:48 +00:00
committed by Automerger Merge Worker
2 changed files with 78 additions and 15 deletions

View File

@@ -37,12 +37,18 @@ public class LocalImageResolver {
private static final String TAG = "LocalImageResolver"; private static final String TAG = "LocalImageResolver";
/** There's no max size specified, load at original size. */
public static final int NO_MAX_SIZE = -1;
@VisibleForTesting @VisibleForTesting
static final int DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480; static final int DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480;
/** /**
* Resolve an image from the given Uri using {@link ImageDecoder} if it contains a * Resolve an image from the given Uri using {@link ImageDecoder} if it contains a
* bitmap reference. * bitmap reference.
* Negative or zero dimensions will result in icon loaded in its original size.
*
* @throws IOException if the icon could not be loaded.
*/ */
@Nullable @Nullable
public static Drawable resolveImage(Uri uri, Context context) throws IOException { public static Drawable resolveImage(Uri uri, Context context) throws IOException {
@@ -63,8 +69,10 @@ public class LocalImageResolver {
* Get the drawable from Icon using {@link ImageDecoder} if it contains a bitmap reference, or * Get the drawable from Icon using {@link ImageDecoder} if it contains a bitmap reference, or
* using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's, * using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's,
* tint, if present, to the drawable. * tint, if present, to the drawable.
* Negative or zero dimensions will result in icon loaded in its original size.
* *
* @return drawable or null if loading failed. * @return drawable or null if the passed icon parameter was null.
* @throws IOException if the icon could not be loaded.
*/ */
@Nullable @Nullable
public static Drawable resolveImage(@Nullable Icon icon, Context context) throws IOException { public static Drawable resolveImage(@Nullable Icon icon, Context context) throws IOException {
@@ -76,8 +84,10 @@ public class LocalImageResolver {
* Get the drawable from Icon using {@link ImageDecoder} if it contains a bitmap reference, or * Get the drawable from Icon using {@link ImageDecoder} if it contains a bitmap reference, or
* using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's, * using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's,
* tint, if present, to the drawable. * tint, if present, to the drawable.
* Negative or zero dimensions will result in icon loaded in its original size.
* *
* @throws IOException if the icon could not be loaded for whichever reason * @return loaded icon or null if a null icon was passed as a parameter.
* @throws IOException if the icon could not be loaded.
*/ */
@Nullable @Nullable
public static Drawable resolveImage(@Nullable Icon icon, Context context, int maxWidth, public static Drawable resolveImage(@Nullable Icon icon, Context context, int maxWidth,
@@ -144,19 +154,22 @@ public class LocalImageResolver {
@Nullable @Nullable
private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth, private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth,
int maxHeight) { int maxHeight) {
Bitmap bitmap = icon.getBitmap();
if (bitmap == null) {
return null;
}
if (bitmap.getWidth() > maxWidth || bitmap.getHeight() > maxHeight) { if (maxWidth > 0 && maxHeight > 0) {
Icon smallerIcon = icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP Bitmap bitmap = icon.getBitmap();
? Icon.createWithAdaptiveBitmap(bitmap) : Icon.createWithBitmap(bitmap); if (bitmap == null) {
// We don't want to modify the source icon, create a copy. return null;
smallerIcon.setTintList(icon.getTintList()) }
.setTintBlendMode(icon.getTintBlendMode())
.scaleDownIfNecessary(maxWidth, maxHeight); if (bitmap.getWidth() > maxWidth || bitmap.getHeight() > maxHeight) {
return smallerIcon.loadDrawable(context); Icon smallerIcon = icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP
? Icon.createWithAdaptiveBitmap(bitmap) : Icon.createWithBitmap(bitmap);
// We don't want to modify the source icon, create a copy.
smallerIcon.setTintList(icon.getTintList())
.setTintBlendMode(icon.getTintBlendMode())
.scaleDownIfNecessary(maxWidth, maxHeight);
return smallerIcon.loadDrawable(context);
}
} }
return icon.loadDrawable(context); return icon.loadDrawable(context);
@@ -202,7 +215,7 @@ public class LocalImageResolver {
// in some cases despite it not saying so. Rethrow it as an IOException to keep // in some cases despite it not saying so. Rethrow it as an IOException to keep
// our API contract. // our API contract.
} catch (IOException | Resources.NotFoundException e) { } catch (IOException | Resources.NotFoundException e) {
Log.e(TAG, "Failed to load image drawable", e); Log.d(TAG, "Couldn't use ImageDecoder for drawable, falling back to non-resized load.");
return null; return null;
} }
} }

View File

@@ -144,6 +144,56 @@ public class LocalImageResolverTest {
assertThat(bd.getBitmap().getHeight()).isLessThan(51); assertThat(bd.getBitmap().getHeight()).isLessThan(51);
} }
@Test
public void resolveImage_largeResourceIcon_negativeWidth_dontResize() {
Icon icon = Icon.createWithResource(mContext, R.drawable.big_a);
Drawable d = LocalImageResolver.resolveImage(icon, mContext, LocalImageResolver.NO_MAX_SIZE,
50);
assertThat(d).isInstanceOf(BitmapDrawable.class);
BitmapDrawable bd = (BitmapDrawable) d;
assertThat(bd.getBitmap().getWidth()).isGreaterThan(101);
assertThat(bd.getBitmap().getHeight()).isGreaterThan(51);
}
@Test
public void resolveImage_largeResourceIcon_negativeHeight_dontResize() {
Icon icon = Icon.createWithResource(mContext, R.drawable.big_a);
Drawable d = LocalImageResolver.resolveImage(icon, mContext, 100,
LocalImageResolver.NO_MAX_SIZE);
assertThat(d).isInstanceOf(BitmapDrawable.class);
BitmapDrawable bd = (BitmapDrawable) d;
assertThat(bd.getBitmap().getWidth()).isGreaterThan(101);
assertThat(bd.getBitmap().getHeight()).isGreaterThan(51);
}
@Test
public void resolveImage_largeBitmapIcon_passedNegativeWidth_dontResize() {
Icon icon = Icon.createWithBitmap(
BitmapFactory.decodeResource(mContext.getResources(), R.drawable.big_a));
Drawable d = LocalImageResolver.resolveImage(icon, mContext, LocalImageResolver.NO_MAX_SIZE,
50);
assertThat(d).isInstanceOf(BitmapDrawable.class);
BitmapDrawable bd = (BitmapDrawable) d;
assertThat(bd.getBitmap().getWidth()).isGreaterThan(101);
assertThat(bd.getBitmap().getHeight()).isGreaterThan(51);
}
@Test
public void resolveImage_largeBitmapIcon_passedNegativeHeight_dontResize() {
Icon icon = Icon.createWithBitmap(
BitmapFactory.decodeResource(mContext.getResources(), R.drawable.big_a));
Drawable d = LocalImageResolver.resolveImage(icon, mContext, LocalImageResolver.NO_MAX_SIZE,
50);
assertThat(d).isInstanceOf(BitmapDrawable.class);
BitmapDrawable bd = (BitmapDrawable) d;
assertThat(bd.getBitmap().getWidth()).isGreaterThan(101);
assertThat(bd.getBitmap().getHeight()).isGreaterThan(51);
}
@Test @Test
public void resolveImage_largeBitmapIcon_passedSize_resizeToDefinedSize() { public void resolveImage_largeBitmapIcon_passedSize_resizeToDefinedSize() {
Icon icon = Icon.createWithBitmap( Icon icon = Icon.createWithBitmap(