From 3405f17fe4165744bb9b78d092cf18f7897d3f2b Mon Sep 17 00:00:00 2001 From: Jernej Virag Date: Thu, 7 Apr 2022 14:56:27 +0000 Subject: [PATCH] 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 --- core/java/android/app/Notification.java | 47 ++++++++++++++ core/res/res/values/dimens.xml | 8 +++ core/res/res/values/symbols.xml | 2 + .../src/android/app/NotificationTest.java | 64 +++++++++++++++++++ 4 files changed, 121 insertions(+) diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 6e1d1cd2e4c9b..6bb35db6e712b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -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 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"; diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index 323c72667c33b..5a6ee22756e98 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -779,6 +779,10 @@ @dimen/notification_icon_circle_start 0.5 + + 144dp @dimen/notification_small_icon_size @@ -792,6 +796,10 @@ 294dp @dimen/notification_right_icon_size + + 96dp 256dp diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index f177226c4ec33..0b13dde483110 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3603,6 +3603,7 @@ + @@ -3611,6 +3612,7 @@ + diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index e6d23643e8c03..a5da4424f7225 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -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();