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

Commit d5160861 authored by Julia Reynolds's avatar Julia Reynolds Committed by Android (Google) Code Review
Browse files

Merge "Read parcelable arrays more safely" into main

parents bbd34d55 9f8cb3b8
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