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

Commit 9f8cb3b8 authored by Julia Reynolds's avatar Julia Reynolds
Browse files

Read parcelable arrays more safely

Test: RemoteInputNotificationRebuilderTest
Test: NotificationEntryTest
Test: NotificationTest
Bug: 370737053
Flag: EXEMPT bug fix
Change-Id: Ie2bf05ba9781a26fa853fbf09c795c32d36d6d99
parent 4eb9e0fa
Loading
Loading
Loading
Loading
+26 −55
Original line number Diff line number Diff line
@@ -1870,7 +1870,7 @@ public class Notification implements Parcelable
         * You can test if a RemoteInput matches these constraints using
         * {@link RemoteInput#isDataOnly}.
         */
        private static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
        static final String EXTRA_DATA_ONLY_INPUTS = "android.extra.DATA_ONLY_INPUTS";
        /**
         * No semantic action defined.
@@ -2089,7 +2089,7 @@ public class Notification implements Parcelable
         * of non-textual RemoteInputs do not access these remote inputs.
         */
        public RemoteInput[] getDataOnlyRemoteInputs() {
            return getParcelableArrayFromBundle(mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
            return mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
        }
        /**
@@ -2330,8 +2330,8 @@ public class Notification implements Parcelable
                checkContextualActionNullFields();
                ArrayList<RemoteInput> dataOnlyInputs = new ArrayList<>();
                RemoteInput[] previousDataInputs = getParcelableArrayFromBundle(
                        mExtras, EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
                RemoteInput[] previousDataInputs =
                        mExtras.getParcelableArray(EXTRA_DATA_ONLY_INPUTS, RemoteInput.class);
                if (previousDataInputs != null) {
                    for (RemoteInput input : previousDataInputs) {
                        dataOnlyInputs.add(input);
@@ -3091,7 +3091,8 @@ public class Notification implements Parcelable
                visitor.accept(Uri.parse(extras.getString(EXTRA_BACKGROUND_IMAGE_URI)));
            }
            ArrayList<Person> people = extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
            ArrayList<Person> people =
                    extras.getParcelableArrayList(EXTRA_PEOPLE_LIST, android.app.Person.class);
            if (people != null && !people.isEmpty()) {
                for (Person p : people) {
                    p.visitUris(visitor);
@@ -3117,8 +3118,8 @@ public class Notification implements Parcelable
                person.visitUris(visitor);
            }
            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
                    Parcelable.class);
            final Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
                    Bundle.class);
            if (!ArrayUtils.isEmpty(messages)) {
                for (MessagingStyle.Message message : MessagingStyle.Message
                        .getMessagesFromBundleArray(messages)) {
@@ -3126,8 +3127,8 @@ public class Notification implements Parcelable
                }
            }
            final Parcelable[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
                    Parcelable.class);
            final Bundle[] historic = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
                    Bundle.class);
            if (!ArrayUtils.isEmpty(historic)) {
                for (MessagingStyle.Message message : MessagingStyle.Message
                        .getMessagesFromBundleArray(historic)) {
@@ -3838,17 +3839,9 @@ public class Notification implements Parcelable
     */
    private void fixDuplicateExtras() {
        if (extras != null) {
            fixDuplicateExtra(mLargeIcon, EXTRA_LARGE_ICON);
        }
            if (mLargeIcon != null) {
                extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon);
            }
    /**
     * 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, Parcelable.class) != null) {
            extras.putParcelable(extraName, original);
        }
    }
@@ -6622,8 +6615,8 @@ public class Notification implements Parcelable
                big.setViewVisibility(R.id.actions_container, View.GONE);
            }
            RemoteInputHistoryItem[] replyText = getParcelableArrayFromBundle(
                    mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
            RemoteInputHistoryItem[] replyText = mN.extras.getParcelableArray(
                    EXTRA_REMOTE_INPUT_HISTORY_ITEMS, RemoteInputHistoryItem.class);
            if (validRemoteInput && replyText != null && replyText.length > 0
                    && !TextUtils.isEmpty(replyText[0].getText())
                    && p.maxRemoteInputHistory > 0) {
@@ -8027,8 +8020,7 @@ public class Notification implements Parcelable
     */
    public boolean hasImage() {
        if (isStyle(MessagingStyle.class) && extras != null) {
            final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES,
                    Parcelable.class);
            final Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Bundle.class);
            if (!ArrayUtils.isEmpty(messages)) {
                for (MessagingStyle.Message m : MessagingStyle.Message
                        .getMessagesFromBundleArray(messages)) {
@@ -9348,10 +9340,10 @@ public class Notification implements Parcelable
                mUser = user;
            }
            mConversationTitle = extras.getCharSequence(EXTRA_CONVERSATION_TITLE);
            Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Parcelable.class);
            Bundle[] messages = extras.getParcelableArray(EXTRA_MESSAGES, Bundle.class);
            mMessages = Message.getMessagesFromBundleArray(messages);
            Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES,
                    Parcelable.class);
                    Bundle.class);
            mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
            mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
            mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
@@ -10218,8 +10210,8 @@ public class Notification implements Parcelable
            if (mBuilder.mActions.size() > 0) {
                maxRows--;
            }
            RemoteInputHistoryItem[] remoteInputHistory = getParcelableArrayFromBundle(
                    mBuilder.mN.extras, EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
            RemoteInputHistoryItem[] remoteInputHistory = mBuilder.mN.extras.getParcelableArray(
                    EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                    RemoteInputHistoryItem.class);
            if (remoteInputHistory != null
                    && remoteInputHistory.length > NUMBER_OF_HISTORY_ALLOWED_UNTIL_REDUCTION) {
@@ -13070,7 +13062,8 @@ public class Notification implements Parcelable
        public WearableExtender(Notification notif) {
            Bundle wearableBundle = notif.extras.getBundle(EXTRA_WEARABLE_EXTENSIONS);
            if (wearableBundle != null) {
                List<Action> actions = wearableBundle.getParcelableArrayList(KEY_ACTIONS, android.app.Notification.Action.class);
                List<Action> actions = wearableBundle.getParcelableArrayList(
                        KEY_ACTIONS, Notification.Action.class);
                if (actions != null) {
                    mActions.addAll(actions);
                }
@@ -13079,8 +13072,8 @@ public class Notification implements Parcelable
                mDisplayIntent = wearableBundle.getParcelable(
                        KEY_DISPLAY_INTENT, PendingIntent.class);
                Notification[] pages = getParcelableArrayFromBundle(
                        wearableBundle, KEY_PAGES, Notification.class);
                Notification[] pages =
                        wearableBundle.getParcelableArray(KEY_PAGES, Notification.class);
                if (pages != null) {
                    Collections.addAll(mPages, pages);
                }
@@ -14015,7 +14008,7 @@ public class Notification implements Parcelable
                if (mParticipants != null && mParticipants.length > 1) {
                    author = mParticipants[0];
                }
                Parcelable[] messages = new Parcelable[mMessages.length];
                Bundle[] messages = new Bundle[mMessages.length];
                for (int i = 0; i < messages.length; i++) {
                    Bundle m = new Bundle();
                    m.putString(KEY_TEXT, mMessages[i]);
@@ -14037,8 +14030,7 @@ public class Notification implements Parcelable
                if (b == null) {
                    return null;
                }
                Parcelable[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES,
                        Parcelable.class);
                Bundle[] parcelableMessages = b.getParcelableArray(KEY_MESSAGES, Bundle.class);
                String[] messages = null;
                if (parcelableMessages != null) {
                    String[] tmp = new String[parcelableMessages.length];
@@ -14402,27 +14394,6 @@ public class Notification implements Parcelable
        }
    }
    /**
     * Get an array of Parcelable objects from a parcelable array bundle field.
     * Update the bundle to have a typed array so fetches in the future don't need
     * to do an array copy.
     */
    @Nullable
    private static <T extends Parcelable> T[] getParcelableArrayFromBundle(
            Bundle bundle, String key, Class<T> itemClass) {
        final Parcelable[] array = bundle.getParcelableArray(key, Parcelable.class);
        final Class<?> arrayClass = Array.newInstance(itemClass, 0).getClass();
        if (arrayClass.isInstance(array) || array == null) {
            return (T[]) array;
        }
        final T[] typedArray = (T[]) Array.newInstance(itemClass, array.length);
        for (int i = 0; i < array.length; i++) {
            typedArray[i] = (T) array[i];
        }
        bundle.putParcelableArray(key, typedArray);
        return typedArray;
    }
    private static class BuilderRemoteViews extends RemoteViews {
        public BuilderRemoteViews(Parcel parcel) {
            super(parcel);
+83 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app;

import static android.app.Notification.Action.EXTRA_DATA_ONLY_INPUTS;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_READ;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_ON_REPLY;
import static android.app.Notification.CarExtender.UnreadConversation.KEY_REMOTE_INPUT;
@@ -97,6 +98,7 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TextAppearanceSpan;
import android.util.Pair;
import android.view.View;
import android.widget.RemoteViews;

import androidx.test.InstrumentationRegistry;
@@ -106,10 +108,10 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.util.ContrastColorUtil;

import junit.framework.Assert;

import libcore.junit.util.compat.CoreCompatChangeRule;

import junit.framework.Assert;

import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -544,6 +546,22 @@ public class NotificationTest {
        assertSame(q.getLargeIcon(), q.extras.getParcelable(EXTRA_LARGE_ICON));
    }

    @Test
    public void largeIconMultipleReferences_ignoreBadData() {
        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);
        n.extras.putParcelable(EXTRA_LARGE_ICON, new NotificationChannelGroup("hi", "hi"));

        Notification q = writeAndReadParcelable(n);
        assertNotSame(q.getLargeIcon(), n.getLargeIcon());

        assertTrue(q.getLargeIcon().getBitmap().sameAs(n.getLargeIcon().getBitmap()));
        assertSame(q.getLargeIcon(), q.extras.getParcelable(EXTRA_LARGE_ICON));
    }

    @Test
    public void largeIconReferenceInExtrasOnly_keptAfterParcelling() {
        Icon originalIcon = Icon.createWithBitmap(BitmapFactory.decodeResource(
@@ -2444,6 +2462,69 @@ public class NotificationTest {

        assertThat(progressStyle1.isStyledByProgress()).isTrue();
    }

    @Test
    public void getDataOnlyRemoteInputs_invalidData() {
        Notification.Action action = new Notification.Action.Builder(0, "title", null)
                .addRemoteInput(new RemoteInput.Builder("result")
                        .setAllowFreeFormInput(false)
                        .setAllowDataType("mimeType", true)
                        .build()).build();
        action.getExtras().putParcelable(EXTRA_DATA_ONLY_INPUTS,
                new NotificationChannelGroup("hi", "hi"));
        assertThat(action.getDataOnlyRemoteInputs()).isNull();
    }

    @Test
    public void actionAddExtras_invalidData() {
        Bundle extras = new Bundle();
        extras.putParcelableArray(EXTRA_DATA_ONLY_INPUTS,
                new NotificationChannelGroup[]{new NotificationChannelGroup("hi", "hi")});
        Notification.Action action = new Notification.Action.Builder(0, "title", null)
                .addRemoteInput(new RemoteInput.Builder("result")
                        .setAllowFreeFormInput(false)
                        .setAllowDataType("mimeType", true)
                        .addExtras(extras)
                        .build()).build();
        assertThat(action.getDataOnlyRemoteInputs()[0].getClass()).isEqualTo(RemoteInput.class);
    }

    @Test
    public void makeBigTemplate_invalidData() {
        PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
                new Intent(mContext, NotificationTest.class),
                PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
        RemoteInput remoteInput =
                new RemoteInput.Builder("key_text_reply")
                        .setLabel("Reply")
                        .setChoices(new String[]{"Choice 1", "Choice 2"})
                        .addExtras(new Bundle())
                        .build();
        Notification.Action replyAction = new Notification.Action.Builder(
                R.drawable.stat_notify_chat, "Reply", pendingIntent)
                .addRemoteInput(remoteInput)
                .build();

        Bundle bundle = new Bundle();
        bundle.putParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                new NotificationChannelGroup[]{});

        Notification.Builder nb = new Notification.Builder(mContext, "channel")
                .setContentTitle("title")
                .setVisibility(android.app.Notification.VISIBILITY_PUBLIC)
                .setStyle(new Notification.InboxStyle())
                .setExtras(bundle)
                .setSmallIcon(R.drawable.stat_notify_chat)
                .addAction(replyAction);

        RemoteViews views = nb.createBigContentView();
        View view = views.apply(mContext, null);
        assertThat(view.findViewById(R.id.notification_material_reply_container).getVisibility())
                .isNotEqualTo(View.VISIBLE);
        assertThat(view.findViewById(R.id.inbox_text0).getVisibility())
                .isNotEqualTo(View.VISIBLE);
    }

    private void assertValid(Notification.Colors c) {
        // Assert that all colors are populated
        assertThat(c.getBackgroundColor()).isNotEqualTo(Notification.COLOR_INVALID);
+27 −5
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.systemui.statusbar;

import static android.app.Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -23,6 +25,7 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import android.app.Notification;
import android.app.NotificationChannelGroup;
import android.app.RemoteInputHistoryItem;
import android.net.Uri;
import android.os.UserHandle;
@@ -70,6 +73,25 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
        mEntry.setRow(mRow);
    }

    @Test
    public void testRebuildWithRemoteInput_invalidData() {
        Uri uri = mock(Uri.class);
        String mimeType = "image/jpeg";
        String text = "image inserted";
        mEntry.getSbn().getNotification().extras.putParcelableArray(
                EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                new NotificationChannelGroup[]{});
        StatusBarNotification newSbn =
                mRebuilder.rebuildWithRemoteInputInserted(
                        mEntry, text, false, mimeType, uri);
        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
        assertEquals(1, messages.length);
        assertEquals(text, messages[0].getText());
        assertEquals(mimeType, messages[0].getMimeType());
        assertEquals(uri, messages[0].getUri());
    }

    @Test
    public void testRebuildWithRemoteInput_noExistingInput_image() {
        Uri uri = mock(Uri.class);
@@ -79,7 +101,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
                mRebuilder.rebuildWithRemoteInputInserted(
                        mEntry, text, false, mimeType, uri);
        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
        assertEquals(1, messages.length);
        assertEquals(text, messages[0].getText());
        assertEquals(mimeType, messages[0].getMimeType());
@@ -92,7 +114,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
                mRebuilder.rebuildWithRemoteInputInserted(
                        mEntry, "A Reply", false, null, null);
        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
        assertEquals(1, messages.length);
        assertEquals("A Reply", messages[0].getText());
        assertFalse(newSbn.getNotification().extras
@@ -107,7 +129,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
                mRebuilder.rebuildWithRemoteInputInserted(
                        mEntry, "A Reply", true, null, null);
        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
        assertEquals(1, messages.length);
        assertEquals("A Reply", messages[0].getText());
        assertTrue(newSbn.getNotification().extras
@@ -130,7 +152,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
        newSbn = mRebuilder.rebuildWithRemoteInputInserted(
                entry, "Reply 2", true, null, null);
        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
        assertEquals(2, messages.length);
        assertEquals("Reply 2", messages[0].getText());
        assertEquals("A Reply", messages[1].getText());
@@ -153,7 +175,7 @@ public class RemoteInputNotificationRebuilderTest extends SysuiTestCase {
        newSbn = mRebuilder.rebuildWithRemoteInputInserted(
                entry, "Reply 2", true, null, null);
        RemoteInputHistoryItem[] messages = (RemoteInputHistoryItem[]) newSbn.getNotification()
                .extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                .extras.getParcelableArray(EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
        assertEquals(2, messages.length);
        assertEquals("Reply 2", messages[0].getText());
        assertEquals(text, messages[1].getText());
+4 −2
Original line number Diff line number Diff line
@@ -128,7 +128,8 @@ public class RemoteInputNotificationRebuilder {

            // Read the whole remoteInputs list from the entry, then append all of those to the sbn.
            Parcelable[] oldHistoryItems = sbn.getNotification().extras
                    .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                    .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                            RemoteInputHistoryItem.class);

            RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
                    ? Stream.concat(
@@ -144,7 +145,8 @@ public class RemoteInputNotificationRebuilder {
                        ? new RemoteInputHistoryItem(mimeType, uri, remoteInputText)
                        : new RemoteInputHistoryItem(remoteInputText);
                Parcelable[] oldHistoryItems = sbn.getNotification().extras
                        .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                        .getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                                RemoteInputHistoryItem.class);
                RemoteInputHistoryItem[] newHistoryItems = oldHistoryItems != null
                        ? Stream.concat(
                                Stream.of(newItem),
+2 −1
Original line number Diff line number Diff line
@@ -586,7 +586,8 @@ public final class NotificationEntry extends ListEntry {
        }
        Bundle extras = mSbn.getNotification().extras;
        Parcelable[] replyTexts =
                extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
                extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS,
                        RemoteInputHistoryItem.class);
        if (!ArrayUtils.isEmpty(replyTexts)) {
            return true;
        }
Loading