Downscale oversized MessagingStyle avatars

Notification API downscales oversized icons in all formats except in MessagingStyle. This seems to be an oversight that can cause massive memory usage.
This downscales avatars to a reasonable size if they're too big.

Bug:193720474
Bug:221890932

Test: atest NotificationTest
      Built a test app with 4000x4000 avatar icon.
      Memory usage went from 250MB to ~170KB per notification.
Change-Id: I35377cdb5d61ee6e23fccc2bffb5d3aec6ca2b7d
This commit is contained in:
Jernej Virag
2022-04-07 14:56:27 +00:00
parent 67936291f7
commit 3405f17fe4
4 changed files with 121 additions and 0 deletions

View File

@@ -7917,6 +7917,8 @@ public class Notification implements Parcelable
* @hide
*/
public MessagingStyle setShortcutIcon(@Nullable Icon conversationIcon) {
// TODO(b/228941516): This icon should be downscaled to avoid using too much memory,
// see reduceImageSizes.
mShortcutIcon = conversationIcon;
return this;
}
@@ -8423,6 +8425,51 @@ public class Notification implements Parcelable
return makeMessagingView(StandardTemplateParams.VIEW_TYPE_HEADS_UP);
}
/**
* @hide
*/
@Override
public void reduceImageSizes(Context context) {
super.reduceImageSizes(context);
Resources resources = context.getResources();
boolean isLowRam = ActivityManager.isLowRamDeviceStatic();
if (mShortcutIcon != null) {
int maxSize = resources.getDimensionPixelSize(
isLowRam ? R.dimen.notification_small_icon_size_low_ram
: R.dimen.notification_small_icon_size);
mShortcutIcon.scaleDownIfNecessary(maxSize, maxSize);
}
int maxAvatarSize = resources.getDimensionPixelSize(
isLowRam ? R.dimen.notification_person_icon_max_size
: R.dimen.notification_person_icon_max_size_low_ram);
if (mUser != null && mUser.getIcon() != null) {
mUser.getIcon().scaleDownIfNecessary(maxAvatarSize, maxAvatarSize);
}
reduceMessagesIconSizes(mMessages, maxAvatarSize);
reduceMessagesIconSizes(mHistoricMessages, maxAvatarSize);
}
/**
* @hide
*/
private static void reduceMessagesIconSizes(@Nullable List<Message> messages, int maxSize) {
if (messages == null) {
return;
}
for (Message message : messages) {
Person sender = message.mSender;
if (sender != null) {
Icon icon = sender.getIcon();
if (icon != null) {
icon.scaleDownIfNecessary(maxSize, maxSize);
}
}
}
}
public static final class Message {
/** @hide */
public static final String KEY_TEXT = "text";

View File

@@ -779,6 +779,10 @@
<dimen name="notification_left_icon_start">@dimen/notification_icon_circle_start</dimen>
<!-- The alpha of a disabled notification button -->
<item type="dimen" format="float" name="notification_action_disabled_alpha">0.5</item>
<!-- The maximum size of Person avatar image in MessagingStyle notifications.
This is bigger than displayed because listeners can use it for other displays
e.g. wearables. -->
<dimen name="notification_person_icon_max_size">144dp</dimen>
<!-- The maximum size of the small notification icon on low memory devices. -->
<dimen name="notification_small_icon_size_low_ram">@dimen/notification_small_icon_size</dimen>
@@ -792,6 +796,10 @@
<dimen name="notification_big_picture_max_width_low_ram">294dp</dimen>
<!-- The size of the right icon image when on low ram -->
<dimen name="notification_right_icon_size_low_ram">@dimen/notification_right_icon_size</dimen>
<!-- The maximum size of Person avatar image in MessagingStyle notifications.
This is bigger than displayed because listeners can use it for other displays
e.g. wearables. -->
<dimen name="notification_person_icon_max_size_low_ram">96dp</dimen>
<!-- The maximum size of the grayscale icon -->
<dimen name="notification_grayscale_icon_max_size">256dp</dimen>

View File

@@ -3603,6 +3603,7 @@
<java-symbol type="dimen" name="notification_actions_icon_drawable_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width"/>
<java-symbol type="dimen" name="notification_person_icon_max_size" />
<java-symbol type="dimen" name="notification_small_icon_size_low_ram"/>
<java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/>
@@ -3611,6 +3612,7 @@
<java-symbol type="dimen" name="notification_grayscale_icon_max_size"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_height_low_ram"/>
<java-symbol type="dimen" name="notification_custom_view_max_image_width_low_ram"/>
<java-symbol type="dimen" name="notification_person_icon_max_size_low_ram" />
<!-- Accessibility fingerprint gestures -->
<java-symbol type="string" name="capability_title_canCaptureFingerprintGestures" />

View File

@@ -520,6 +520,70 @@ public class NotificationTest {
R.dimen.notification_small_icon_size));
}
@Test
public void testBuild_ensureMessagingUserIsNotTooBig_resizesIcon() {
Icon hugeIcon = Icon.createWithBitmap(
Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
Icon hugeMessageAvatar = Icon.createWithBitmap(
Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
Icon hugeHistoricMessageAvatar = Icon.createWithBitmap(
Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
Notification.MessagingStyle style = new Notification.MessagingStyle(
new Person.Builder().setIcon(hugeIcon).setName("A User").build());
style.addMessage(new Notification.MessagingStyle.Message("A message", 123456,
new Person.Builder().setIcon(hugeMessageAvatar).setName("A Sender").build()));
style.addHistoricMessage(new Notification.MessagingStyle.Message("A message", 123456,
new Person.Builder().setIcon(hugeHistoricMessageAvatar).setName(
"A Historic Sender").build()));
Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
style).build();
Bitmap personIcon = style.getUser().getIcon().getBitmap();
assertThat(personIcon.getWidth()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_person_icon_max_size));
assertThat(personIcon.getHeight()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_person_icon_max_size));
Bitmap avatarIcon = style.getMessages().get(0).getSenderPerson().getIcon().getBitmap();
assertThat(avatarIcon.getWidth()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_person_icon_max_size));
assertThat(avatarIcon.getHeight()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_person_icon_max_size));
Bitmap historicAvatarIcon = style.getHistoricMessages().get(
0).getSenderPerson().getIcon().getBitmap();
assertThat(historicAvatarIcon.getWidth()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_person_icon_max_size));
assertThat(historicAvatarIcon.getHeight()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_person_icon_max_size));
}
@Test
public void testBuild_ensureMessagingShortcutIconIsNotTooBig_resizesIcon() {
Icon hugeIcon = Icon.createWithBitmap(
Bitmap.createBitmap(3000, 3000, Bitmap.Config.ARGB_8888));
Notification.MessagingStyle style = new Notification.MessagingStyle(
new Person.Builder().setName("A User").build()).setShortcutIcon(hugeIcon);
Notification notification = new Notification.Builder(mContext, "Channel").setStyle(
style).build();
Bitmap shortcutIcon = style.getShortcutIcon().getBitmap();
assertThat(shortcutIcon.getWidth()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_small_icon_size));
assertThat(shortcutIcon.getHeight()).isEqualTo(
mContext.getResources().getDimensionPixelSize(
R.dimen.notification_small_icon_size));
}
@Test
public void testColors_ensureColors_dayMode_producesValidPalette() {
Notification.Colors c = new Notification.Colors();