Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ad7e72ac authored by Robin Lee's avatar Robin Lee
Browse files

Remove duplicate bitmaps from Notification parcels

Created when the notification is marshalled and then unmarshalled
across process boundaries, since there are multiple references to the
same image from different places and they all get sent as separate
objects.

Fix: 70152868
Test: make FrameworksCoreTests -j30 && adb install -r ${ANDROID_PRODUCT_OUT}/data/app/FrameworksCoreTests/FrameworksCoreTests.apk && adb shell am instrument -e class android.app.NotificationTest -w com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I0a8c95207ab42260d88e5c206a04ab8386376ac4
parent 70de9728
Loading
Loading
Loading
Loading
+28 −0
Original line number Original line Diff line number Diff line
@@ -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


@@ -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.
+46 −0
Original line number Original line Diff line number Diff line
@@ -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;
@@ -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);
@@ -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);
    }
}
}