Loading core/java/android/app/Notification.java +28 −0 Original line number Original line Diff line number Diff line Loading @@ -1941,6 +1941,7 @@ public class Notification implements Parcelable mSortKey = parcel.readString(); mSortKey = parcel.readString(); extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null fixDuplicateExtras(); actions = parcel.createTypedArray(Action.CREATOR); // may be null actions = parcel.createTypedArray(Action.CREATOR); // may be null Loading Loading @@ -2388,6 +2389,33 @@ public class Notification implements Parcelable } } }; }; /** * Parcelling creates multiple copies of objects in {@code extras}. Fix them. * <p> * For backwards compatibility {@code extras} holds some references to "real" member data such * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly * fine as long as the object stays in one process. * <p> * However, once the notification goes into a parcel each reference gets marshalled separately, * wasting memory. Especially with large images on Auto and TV, this is worth fixing. */ private void fixDuplicateExtras() { if (extras != null) { fixDuplicateExtra(mSmallIcon, EXTRA_SMALL_ICON); fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); } } /** * If we find an extra that's exactly the same as one of the "real" fields but refers to a * separate object, replace it with the field's version to avoid holding duplicate copies. */ private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { if (original != null && extras.getParcelable(extraName) != null) { extras.putParcelable(extraName, original); } } /** /** * Sets the {@link #contentView} field to be a view with the standard "Latest Event" * Sets the {@link #contentView} field to be a view with the standard "Latest Event" * layout. * layout. Loading core/tests/coretests/src/android/app/NotificationTest.java +46 −0 Original line number Original line Diff line number Diff line Loading @@ -19,12 +19,17 @@ package android.app; import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast; import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.media.session.MediaSession; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4; Loading Loading @@ -141,6 +146,36 @@ public class NotificationTest { assertFalse(n.hasCompletedProgress()); assertFalse(n.hasCompletedProgress()); } } @Test public void largeIconMultipleReferences_keptAfterParcelling() { Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource( mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96)); Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build(); assertSame(n.getLargeIcon(), originalIcon); Notification q = writeAndReadParcelable(n); assertNotSame(q.getLargeIcon(), n.getLargeIcon()); assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap())); assertSame(q.getLargeIcon(), q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)); } @Test public void largeIconReferenceInExtrasOnly_keptAfterParcelling() { Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource( mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96)); Notification n = new Notification.Builder(mContext).build(); n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, originalIcon); assertSame(n.getLargeIcon(), null); Notification q = writeAndReadParcelable(n); assertSame(q.getLargeIcon(), null); assertTrue(((Icon) q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)).getBitmap() .sameAs(originalIcon.getBitmap())); } @Test @Test public void allPendingIntents_recollectedAfterReusingBuilder() { public void allPendingIntents_recollectedAfterReusingBuilder() { PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0); PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0); Loading Loading @@ -187,4 +222,15 @@ public class NotificationTest { .setContentText("Text") .setContentText("Text") .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken())); .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken())); } } /** * Writes an arbitrary {@link Parcelable} into a {@link Parcel} using its writeToParcel * method before reading it out again to check that it was sent properly. */ private static <T extends Parcelable> T writeAndReadParcelable(T original) { Parcel p = Parcel.obtain(); p.writeParcelable(original, /* flags */ 0); p.setDataPosition(0); return p.readParcelable(/* classLoader */ null); } } } Loading
core/java/android/app/Notification.java +28 −0 Original line number Original line Diff line number Diff line Loading @@ -1941,6 +1941,7 @@ public class Notification implements Parcelable mSortKey = parcel.readString(); mSortKey = parcel.readString(); extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null fixDuplicateExtras(); actions = parcel.createTypedArray(Action.CREATOR); // may be null actions = parcel.createTypedArray(Action.CREATOR); // may be null Loading Loading @@ -2388,6 +2389,33 @@ public class Notification implements Parcelable } } }; }; /** * Parcelling creates multiple copies of objects in {@code extras}. Fix them. * <p> * For backwards compatibility {@code extras} holds some references to "real" member data such * as {@link getLargeIcon()} which is mirrored by {@link #EXTRA_LARGE_ICON}. This is mostly * fine as long as the object stays in one process. * <p> * However, once the notification goes into a parcel each reference gets marshalled separately, * wasting memory. Especially with large images on Auto and TV, this is worth fixing. */ private void fixDuplicateExtras() { if (extras != null) { fixDuplicateExtra(mSmallIcon, EXTRA_SMALL_ICON); fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON); } } /** * If we find an extra that's exactly the same as one of the "real" fields but refers to a * separate object, replace it with the field's version to avoid holding duplicate copies. */ private void fixDuplicateExtra(@Nullable Parcelable original, @NonNull String extraName) { if (original != null && extras.getParcelable(extraName) != null) { extras.putParcelable(extraName, original); } } /** /** * Sets the {@link #contentView} field to be a view with the standard "Latest Event" * Sets the {@link #contentView} field to be a view with the standard "Latest Event" * layout. * layout. Loading
core/tests/coretests/src/android/app/NotificationTest.java +46 −0 Original line number Original line Diff line number Diff line Loading @@ -19,12 +19,17 @@ package android.app; import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast; import static com.android.internal.util.NotificationColorUtil.satisfiesTextContrast; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue; import android.content.Context; import android.content.Context; import android.content.Intent; import android.content.Intent; import android.graphics.BitmapFactory; import android.graphics.drawable.Icon; import android.media.session.MediaSession; import android.media.session.MediaSession; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.support.test.InstrumentationRegistry; import android.support.test.InstrumentationRegistry; import android.support.test.filters.SmallTest; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; import android.support.test.runner.AndroidJUnit4; Loading Loading @@ -141,6 +146,36 @@ public class NotificationTest { assertFalse(n.hasCompletedProgress()); assertFalse(n.hasCompletedProgress()); } } @Test public void largeIconMultipleReferences_keptAfterParcelling() { Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource( mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96)); Notification n = new Notification.Builder(mContext).setLargeIcon(originalIcon).build(); assertSame(n.getLargeIcon(), originalIcon); Notification q = writeAndReadParcelable(n); assertNotSame(q.getLargeIcon(), n.getLargeIcon()); assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap())); assertSame(q.getLargeIcon(), q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)); } @Test public void largeIconReferenceInExtrasOnly_keptAfterParcelling() { Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource( mContext.getResources(), com.android.frameworks.coretests.R.drawable.test128x96)); Notification n = new Notification.Builder(mContext).build(); n.extras.putParcelable(Notification.EXTRA_LARGE_ICON, originalIcon); assertSame(n.getLargeIcon(), null); Notification q = writeAndReadParcelable(n); assertSame(q.getLargeIcon(), null); assertTrue(((Icon) q.extras.getParcelable(Notification.EXTRA_LARGE_ICON)).getBitmap() .sameAs(originalIcon.getBitmap())); } @Test @Test public void allPendingIntents_recollectedAfterReusingBuilder() { public void allPendingIntents_recollectedAfterReusingBuilder() { PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0); PendingIntent intent1 = PendingIntent.getActivity(mContext, 0, new Intent("test1"), 0); Loading Loading @@ -187,4 +222,15 @@ public class NotificationTest { .setContentText("Text") .setContentText("Text") .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken())); .setStyle(new Notification.MediaStyle().setMediaSession(session.getSessionToken())); } } /** * Writes an arbitrary {@link Parcelable} into a {@link Parcel} using its writeToParcel * method before reading it out again to check that it was sent properly. */ private static <T extends Parcelable> T writeAndReadParcelable(T original) { Parcel p = Parcel.obtain(); p.writeParcelable(original, /* flags */ 0); p.setDataPosition(0); return p.readParcelable(/* classLoader */ null); } } }