Fix handling of negative size request for Bitmaps in LocalImageResolver

Requesting negative sized bitmap should mean "no restrictions" and not "please resize to -1x-1". This fixes the corner case for pure bitmaps.
Also reduce severity of logging when we're falling back to non-ImageDecoder load since that's not a catastrophic failure.

Bug:227103943
Bug:227008805

Test: tested on device with Notify1.apk while reproducing the issue
      atest LocalImageResolver with new tests
Change-Id: I581d3e637fcafaba68856738f90cc72c42442d60
This commit is contained in:
Jernej Virag
2022-03-30 14:16:57 +00:00
parent 0d8aaf4b6b
commit 26e492cc4b
2 changed files with 78 additions and 15 deletions

View File

@@ -37,12 +37,18 @@ public class 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
static final int DEFAULT_MAX_SAFE_ICON_SIZE_PX = 480;
/**
* Resolve an image from the given Uri using {@link ImageDecoder} if it contains a
* 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
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
* using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's,
* 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
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
* using {@link Icon#loadDrawable(Context)} otherwise. This will correctly apply the Icon's,
* 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
public static Drawable resolveImage(@Nullable Icon icon, Context context, int maxWidth,
@@ -144,19 +154,22 @@ public class LocalImageResolver {
@Nullable
private static Drawable resolveBitmapImage(Icon icon, Context context, int maxWidth,
int maxHeight) {
Bitmap bitmap = icon.getBitmap();
if (bitmap == null) {
return null;
}
if (bitmap.getWidth() > maxWidth || bitmap.getHeight() > maxHeight) {
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);
if (maxWidth > 0 && maxHeight > 0) {
Bitmap bitmap = icon.getBitmap();
if (bitmap == null) {
return null;
}
if (bitmap.getWidth() > maxWidth || bitmap.getHeight() > maxHeight) {
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);
@@ -202,7 +215,7 @@ public class LocalImageResolver {
// in some cases despite it not saying so. Rethrow it as an IOException to keep
// our API contract.
} 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;
}
}

View File

@@ -144,6 +144,56 @@ public class LocalImageResolverTest {
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
public void resolveImage_largeBitmapIcon_passedSize_resizeToDefinedSize() {
Icon icon = Icon.createWithBitmap(