Merge "Dramatically simplify NotificationRankingUpdate." into qt-dev
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index 3ec21e3..e02fd9f 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -42,7 +42,6 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -53,7 +52,6 @@
 import android.os.ServiceManager;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Log;
 import android.widget.RemoteViews;
 
@@ -64,8 +62,8 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A service that receives calls from the system when new notifications are
@@ -1442,7 +1440,7 @@
      */
     @GuardedBy("mLock")
     public final void applyUpdateLocked(NotificationRankingUpdate update) {
-        mRankingMap = new RankingMap(update);
+        mRankingMap = update.getRankingMap();
     }
 
     /** @hide */
@@ -1480,14 +1478,14 @@
          */
         public static final int USER_SENTIMENT_POSITIVE = 1;
 
-        /** @hide */
+       /** @hide */
         @IntDef(prefix = { "USER_SENTIMENT_" }, value = {
                 USER_SENTIMENT_NEGATIVE, USER_SENTIMENT_NEUTRAL, USER_SENTIMENT_POSITIVE
         })
         @Retention(RetentionPolicy.SOURCE)
         public @interface UserSentiment {}
 
-        private String mKey;
+        private @NonNull String mKey;
         private int mRank = -1;
         private boolean mIsAmbient;
         private boolean mMatchesInterruptionFilter;
@@ -1512,7 +1510,70 @@
         private ArrayList<CharSequence> mSmartReplies;
         private boolean mCanBubble;
 
-        public Ranking() {}
+        private static final int PARCEL_VERSION = 2;
+
+        public Ranking() { }
+
+        // You can parcel it, but it's not Parcelable
+        /** @hide */
+        @VisibleForTesting
+        public void writeToParcel(Parcel out, int flags) {
+            final long start = out.dataPosition();
+            out.writeInt(PARCEL_VERSION);
+            out.writeString(mKey);
+            out.writeInt(mRank);
+            out.writeBoolean(mIsAmbient);
+            out.writeBoolean(mMatchesInterruptionFilter);
+            out.writeInt(mVisibilityOverride);
+            out.writeInt(mSuppressedVisualEffects);
+            out.writeInt(mImportance);
+            out.writeCharSequence(mImportanceExplanation);
+            out.writeString(mOverrideGroupKey);
+            out.writeParcelable(mChannel, flags);
+            out.writeStringList(mOverridePeople);
+            out.writeTypedList(mSnoozeCriteria, flags);
+            out.writeBoolean(mShowBadge);
+            out.writeInt(mUserSentiment);
+            out.writeBoolean(mHidden);
+            out.writeLong(mLastAudiblyAlertedMs);
+            out.writeBoolean(mNoisy);
+            out.writeTypedList(mSmartActions, flags);
+            out.writeCharSequenceList(mSmartReplies);
+            out.writeBoolean(mCanBubble);
+        }
+
+        /** @hide */
+        @VisibleForTesting
+        public Ranking(Parcel in) {
+            final ClassLoader cl = getClass().getClassLoader();
+
+            final int version = in.readInt();
+            if (version != PARCEL_VERSION) {
+                throw new IllegalArgumentException("malformed Ranking parcel: " + in + " version "
+                        + version + ", expected " + PARCEL_VERSION);
+            }
+            mKey = in.readString();
+            mRank = in.readInt();
+            mIsAmbient = in.readBoolean();
+            mMatchesInterruptionFilter = in.readBoolean();
+            mVisibilityOverride = in.readInt();
+            mSuppressedVisualEffects = in.readInt();
+            mImportance = in.readInt();
+            mImportanceExplanation = in.readCharSequence(); // may be null
+            mOverrideGroupKey = in.readString(); // may be null
+            mChannel = (NotificationChannel) in.readParcelable(cl); // may be null
+            mOverridePeople = in.createStringArrayList();
+            mSnoozeCriteria = in.createTypedArrayList(SnoozeCriterion.CREATOR);
+            mShowBadge = in.readBoolean();
+            mUserSentiment = in.readInt();
+            mHidden = in.readBoolean();
+            mLastAudiblyAlertedMs = in.readLong();
+            mNoisy = in.readBoolean();
+            mSmartActions = in.createTypedArrayList(Notification.Action.CREATOR);
+            mSmartReplies = in.readCharSequenceList();
+            mCanBubble = in.readBoolean();
+        }
+
 
         /**
          * Returns the key of the notification this Ranking applies to.
@@ -1737,6 +1798,31 @@
         }
 
         /**
+         * @hide
+         */
+        public void populate(Ranking other) {
+            populate(other.mKey,
+                    other.mRank,
+                    other.mMatchesInterruptionFilter,
+                    other.mVisibilityOverride,
+                    other.mSuppressedVisualEffects,
+                    other.mImportance,
+                    other.mImportanceExplanation,
+                    other.mOverrideGroupKey,
+                    other.mChannel,
+                    other.mOverridePeople,
+                    other.mSnoozeCriteria,
+                    other.mShowBadge,
+                    other.mUserSentiment,
+                    other.mHidden,
+                    other.mLastAudiblyAlertedMs,
+                    other.mNoisy,
+                    other.mSmartActions,
+                    other.mSmartReplies,
+                    other.mCanBubble);
+        }
+
+        /**
          * {@hide}
          */
         public static String importanceToString(int importance) {
@@ -1758,6 +1844,35 @@
                     return "UNKNOWN(" + String.valueOf(importance) + ")";
             }
         }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            Ranking other = (Ranking) o;
+            return Objects.equals(mKey, other.mKey)
+                    && Objects.equals(mRank, other.mRank)
+                    && Objects.equals(mMatchesInterruptionFilter, other.mMatchesInterruptionFilter)
+                    && Objects.equals(mVisibilityOverride, other.mVisibilityOverride)
+                    && Objects.equals(mSuppressedVisualEffects, other.mSuppressedVisualEffects)
+                    && Objects.equals(mImportance, other.mImportance)
+                    && Objects.equals(mImportanceExplanation, other.mImportanceExplanation)
+                    && Objects.equals(mOverrideGroupKey, other.mOverrideGroupKey)
+                    && Objects.equals(mChannel, other.mChannel)
+                    && Objects.equals(mOverridePeople, other.mOverridePeople)
+                    && Objects.equals(mSnoozeCriteria, other.mSnoozeCriteria)
+                    && Objects.equals(mShowBadge, other.mShowBadge)
+                    && Objects.equals(mUserSentiment, other.mUserSentiment)
+                    && Objects.equals(mHidden, other.mHidden)
+                    && Objects.equals(mLastAudiblyAlertedMs, other.mLastAudiblyAlertedMs)
+                    && Objects.equals(mNoisy, other.mNoisy)
+                    // Action.equals() doesn't exist so let's just compare list lengths
+                    && ((mSmartActions == null ? 0 : mSmartActions.size())
+                        == (other.mSmartActions == null ? 0 : other.mSmartActions.size()))
+                    && Objects.equals(mSmartReplies, other.mSmartReplies)
+                    && Objects.equals(mCanBubble, other.mCanBubble);
+        }
     }
 
     /**
@@ -1769,30 +1884,74 @@
      * notifications active at the time of retrieval.
      */
     public static class RankingMap implements Parcelable {
-        private final NotificationRankingUpdate mRankingUpdate;
-        private ArrayMap<String,Integer> mRanks;
-        private ArraySet<Object> mIntercepted;
-        private ArrayMap<String, Integer> mVisibilityOverrides;
-        private ArrayMap<String, Integer> mSuppressedVisualEffects;
-        private ArrayMap<String, Integer> mImportance;
-        private ArrayMap<String, String> mImportanceExplanation;
-        private ArrayMap<String, String> mOverrideGroupKeys;
-        private ArrayMap<String, NotificationChannel> mChannels;
-        private ArrayMap<String, ArrayList<String>> mOverridePeople;
-        private ArrayMap<String, ArrayList<SnoozeCriterion>> mSnoozeCriteria;
-        private ArrayMap<String, Boolean> mShowBadge;
-        private ArrayMap<String, Integer> mUserSentiment;
-        private ArrayMap<String, Boolean> mHidden;
-        private ArrayMap<String, Long> mLastAudiblyAlerted;
-        private ArrayMap<String, Boolean> mNoisy;
-        private ArrayMap<String, ArrayList<Notification.Action>> mSmartActions;
-        private ArrayMap<String, ArrayList<CharSequence>> mSmartReplies;
-        private boolean[] mCanBubble;
+        private ArrayList<String> mOrderedKeys = new ArrayList<>();
+        // Note: all String keys should be intern'd as pointers into mOrderedKeys
+        private ArrayMap<String, Ranking> mRankings = new ArrayMap<>();
 
-        private RankingMap(NotificationRankingUpdate rankingUpdate) {
-            mRankingUpdate = rankingUpdate;
+        /**
+         * @hide
+         */
+        public RankingMap(Ranking[] rankings) {
+            for (int i = 0; i < rankings.length; i++) {
+                final String key = rankings[i].getKey();
+                mOrderedKeys.add(key);
+                mRankings.put(key, rankings[i]);
+            }
         }
 
+        // -- parcelable interface --
+
+        private RankingMap(Parcel in) {
+            final ClassLoader cl = getClass().getClassLoader();
+            final int count = in.readInt();
+            mOrderedKeys.ensureCapacity(count);
+            mRankings.ensureCapacity(count);
+            for (int i = 0; i < count; i++) {
+                final Ranking r = new Ranking(in);
+                final String key = r.getKey();
+                mOrderedKeys.add(key);
+                mRankings.put(key, r);
+            }
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            RankingMap other = (RankingMap) o;
+
+            return mOrderedKeys.equals(other.mOrderedKeys)
+                    && mRankings.equals(other.mRankings);
+
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            final int count = mOrderedKeys.size();
+            out.writeInt(count);
+            for (int i = 0; i < count; i++) {
+                mRankings.get(mOrderedKeys.get(i)).writeToParcel(out, flags);
+            }
+        }
+
+        public static final @android.annotation.NonNull Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
+            @Override
+            public RankingMap createFromParcel(Parcel source) {
+                return new RankingMap(source);
+            }
+
+            @Override
+            public RankingMap[] newArray(int size) {
+                return new RankingMap[size];
+            }
+        };
+
         /**
          * Request the list of notification keys in their current ranking
          * order.
@@ -1800,7 +1959,7 @@
          * @return An array of active notification keys, in their ranking order.
          */
         public String[] getOrderedKeys() {
-            return mRankingUpdate.getOrderedKeys();
+            return mOrderedKeys.toArray(new String[0]);
         }
 
         /**
@@ -1808,381 +1967,26 @@
          * with the given key.
          *
          * @return true if a valid key has been passed and outRanking has
-         *     been populated; false otherwise
+         * been populated; false otherwise
          */
         public boolean getRanking(String key, Ranking outRanking) {
-            int rank = getRank(key);
-            outRanking.populate(key, rank, !isIntercepted(key),
-                    getVisibilityOverride(key), getSuppressedVisualEffects(key),
-                    getImportance(key), getImportanceExplanation(key), getOverrideGroupKey(key),
-                    getChannel(key), getOverridePeople(key), getSnoozeCriteria(key),
-                    getShowBadge(key), getUserSentiment(key), getHidden(key),
-                    getLastAudiblyAlerted(key), getNoisy(key), getSmartActions(key),
-                    getSmartReplies(key), canBubble(key));
-            return rank >= 0;
-        }
-
-        private int getRank(String key) {
-            synchronized (this) {
-                if (mRanks == null) {
-                    buildRanksLocked();
-                }
+            if (mRankings.containsKey(key)) {
+                outRanking.populate(mRankings.get(key));
+                return true;
             }
-            Integer rank = mRanks.get(key);
-            return rank != null ? rank : -1;
+            return false;
         }
 
-        private boolean isIntercepted(String key) {
-            synchronized (this) {
-                if (mIntercepted == null) {
-                    buildInterceptedSetLocked();
-                }
-            }
-            return mIntercepted.contains(key);
+        /**
+         * Get a reference to the actual Ranking object corresponding to the key.
+         * Used only by unit tests.
+         *
+         * @hide
+         */
+        @VisibleForTesting
+        public Ranking getRawRankingObject(String key) {
+            return mRankings.get(key);
         }
-
-        private int getVisibilityOverride(String key) {
-            synchronized (this) {
-                if (mVisibilityOverrides == null) {
-                    buildVisibilityOverridesLocked();
-                }
-            }
-            Integer override = mVisibilityOverrides.get(key);
-            if (override == null) {
-                return Ranking.VISIBILITY_NO_OVERRIDE;
-            }
-            return override.intValue();
-        }
-
-        private int getSuppressedVisualEffects(String key) {
-            synchronized (this) {
-                if (mSuppressedVisualEffects == null) {
-                    buildSuppressedVisualEffectsLocked();
-                }
-            }
-            Integer suppressed = mSuppressedVisualEffects.get(key);
-            if (suppressed == null) {
-                return 0;
-            }
-            return suppressed.intValue();
-        }
-
-        private int getImportance(String key) {
-            synchronized (this) {
-                if (mImportance == null) {
-                    buildImportanceLocked();
-                }
-            }
-            Integer importance = mImportance.get(key);
-            if (importance == null) {
-                return NotificationManager.IMPORTANCE_DEFAULT;
-            }
-            return importance.intValue();
-        }
-
-        private String getImportanceExplanation(String key) {
-            synchronized (this) {
-                if (mImportanceExplanation == null) {
-                    buildImportanceExplanationLocked();
-                }
-            }
-            return mImportanceExplanation.get(key);
-        }
-
-        private String getOverrideGroupKey(String key) {
-            synchronized (this) {
-                if (mOverrideGroupKeys == null) {
-                    buildOverrideGroupKeys();
-                }
-            }
-            return mOverrideGroupKeys.get(key);
-        }
-
-        private NotificationChannel getChannel(String key) {
-            synchronized (this) {
-                if (mChannels == null) {
-                    buildChannelsLocked();
-                }
-            }
-            return mChannels.get(key);
-        }
-
-        private ArrayList<String> getOverridePeople(String key) {
-            synchronized (this) {
-                if (mOverridePeople == null) {
-                    buildOverridePeopleLocked();
-                }
-            }
-            return mOverridePeople.get(key);
-        }
-
-        private ArrayList<SnoozeCriterion> getSnoozeCriteria(String key) {
-            synchronized (this) {
-                if (mSnoozeCriteria == null) {
-                    buildSnoozeCriteriaLocked();
-                }
-            }
-            return mSnoozeCriteria.get(key);
-        }
-
-        private boolean getShowBadge(String key) {
-            synchronized (this) {
-                if (mShowBadge == null) {
-                    buildShowBadgeLocked();
-                }
-            }
-            Boolean showBadge = mShowBadge.get(key);
-            return showBadge == null ? false : showBadge.booleanValue();
-        }
-
-        private int getUserSentiment(String key) {
-            synchronized (this) {
-                if (mUserSentiment == null) {
-                    buildUserSentimentLocked();
-                }
-            }
-            Integer userSentiment = mUserSentiment.get(key);
-            return userSentiment == null
-                    ? Ranking.USER_SENTIMENT_NEUTRAL : userSentiment.intValue();
-        }
-
-        private boolean getHidden(String key) {
-            synchronized (this) {
-                if (mHidden == null) {
-                    buildHiddenLocked();
-                }
-            }
-            Boolean hidden = mHidden.get(key);
-            return hidden == null ? false : hidden.booleanValue();
-        }
-
-        private long getLastAudiblyAlerted(String key) {
-            synchronized (this) {
-                if (mLastAudiblyAlerted == null) {
-                    buildLastAudiblyAlertedLocked();
-                }
-            }
-            Long lastAudibleAlerted = mLastAudiblyAlerted.get(key);
-            return lastAudibleAlerted == null ? -1 : lastAudibleAlerted.longValue();
-        }
-
-        private boolean getNoisy(String key) {
-            synchronized (this) {
-                if (mNoisy == null) {
-                    buildNoisyLocked();
-                }
-            }
-            Boolean noisy = mNoisy.get(key);
-            return noisy == null ? false : noisy.booleanValue();
-        }
-
-        private ArrayList<Notification.Action> getSmartActions(String key) {
-            synchronized (this) {
-                if (mSmartActions == null) {
-                    buildSmartActions();
-                }
-            }
-            return mSmartActions.get(key);
-        }
-
-        private ArrayList<CharSequence> getSmartReplies(String key) {
-            synchronized (this) {
-                if (mSmartReplies == null) {
-                    buildSmartReplies();
-                }
-            }
-            return mSmartReplies.get(key);
-        }
-
-        private boolean canBubble(String key) {
-            synchronized (this) {
-                if (mRanks == null) {
-                    buildRanksLocked();
-                }
-                if (mCanBubble == null) {
-                    mCanBubble = mRankingUpdate.getCanBubble();
-                }
-            }
-            int keyIndex = mRanks.getOrDefault(key, -1);
-            return keyIndex >= 0 ? mCanBubble[keyIndex] : false;
-        }
-
-        // Locked by 'this'
-        private void buildRanksLocked() {
-            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
-            mRanks = new ArrayMap<>(orderedKeys.length);
-            for (int i = 0; i < orderedKeys.length; i++) {
-                String key = orderedKeys[i];
-                mRanks.put(key, i);
-            }
-        }
-
-        // Locked by 'this'
-        private void buildInterceptedSetLocked() {
-            String[] dndInterceptedKeys = mRankingUpdate.getInterceptedKeys();
-            mIntercepted = new ArraySet<>(dndInterceptedKeys.length);
-            Collections.addAll(mIntercepted, dndInterceptedKeys);
-        }
-
-        private ArrayMap<String, Integer> buildIntMapFromBundle(Bundle bundle) {
-            ArrayMap<String, Integer> newMap = new ArrayMap<>(bundle.size());
-            for (String key : bundle.keySet()) {
-                newMap.put(key, bundle.getInt(key));
-            }
-            return newMap;
-        }
-
-        private ArrayMap<String, String> buildStringMapFromBundle(Bundle bundle) {
-            ArrayMap<String, String> newMap = new ArrayMap<>(bundle.size());
-            for (String key : bundle.keySet()) {
-                newMap.put(key, bundle.getString(key));
-            }
-            return newMap;
-        }
-
-        private ArrayMap<String, Boolean> buildBooleanMapFromBundle(Bundle bundle) {
-            ArrayMap<String, Boolean> newMap = new ArrayMap<>(bundle.size());
-            for (String key : bundle.keySet()) {
-                newMap.put(key, bundle.getBoolean(key));
-            }
-            return newMap;
-        }
-
-        private ArrayMap<String, Long> buildLongMapFromBundle(Bundle bundle) {
-            ArrayMap<String, Long> newMap = new ArrayMap<>(bundle.size());
-            for (String key : bundle.keySet()) {
-                newMap.put(key, bundle.getLong(key));
-            }
-            return newMap;
-        }
-
-        // Locked by 'this'
-        private void buildVisibilityOverridesLocked() {
-            mVisibilityOverrides = buildIntMapFromBundle(mRankingUpdate.getVisibilityOverrides());
-        }
-
-        // Locked by 'this'
-        private void buildSuppressedVisualEffectsLocked() {
-            mSuppressedVisualEffects =
-                buildIntMapFromBundle(mRankingUpdate.getSuppressedVisualEffects());
-        }
-
-        // Locked by 'this'
-        private void buildImportanceLocked() {
-            String[] orderedKeys = mRankingUpdate.getOrderedKeys();
-            int[] importance = mRankingUpdate.getImportance();
-            mImportance = new ArrayMap<>(orderedKeys.length);
-            for (int i = 0; i < orderedKeys.length; i++) {
-                String key = orderedKeys[i];
-                mImportance.put(key, importance[i]);
-            }
-        }
-
-        // Locked by 'this'
-        private void buildImportanceExplanationLocked() {
-            mImportanceExplanation =
-                buildStringMapFromBundle(mRankingUpdate.getImportanceExplanation());
-        }
-
-        // Locked by 'this'
-        private void buildOverrideGroupKeys() {
-            mOverrideGroupKeys = buildStringMapFromBundle(mRankingUpdate.getOverrideGroupKeys());
-        }
-
-        // Locked by 'this'
-        private void buildChannelsLocked() {
-            Bundle channels = mRankingUpdate.getChannels();
-            mChannels = new ArrayMap<>(channels.size());
-            for (String key : channels.keySet()) {
-                mChannels.put(key, channels.getParcelable(key));
-            }
-        }
-
-        // Locked by 'this'
-        private void buildOverridePeopleLocked() {
-            Bundle overridePeople = mRankingUpdate.getOverridePeople();
-            mOverridePeople = new ArrayMap<>(overridePeople.size());
-            for (String key : overridePeople.keySet()) {
-                mOverridePeople.put(key, overridePeople.getStringArrayList(key));
-            }
-        }
-
-        // Locked by 'this'
-        private void buildSnoozeCriteriaLocked() {
-            Bundle snoozeCriteria = mRankingUpdate.getSnoozeCriteria();
-            mSnoozeCriteria = new ArrayMap<>(snoozeCriteria.size());
-            for (String key : snoozeCriteria.keySet()) {
-                mSnoozeCriteria.put(key, snoozeCriteria.getParcelableArrayList(key));
-            }
-        }
-
-        // Locked by 'this'
-        private void buildShowBadgeLocked() {
-            mShowBadge = buildBooleanMapFromBundle(mRankingUpdate.getShowBadge());
-        }
-
-        // Locked by 'this'
-        private void buildUserSentimentLocked() {
-            mUserSentiment = buildIntMapFromBundle(mRankingUpdate.getUserSentiment());
-        }
-
-        // Locked by 'this'
-        private void buildHiddenLocked() {
-            mHidden = buildBooleanMapFromBundle(mRankingUpdate.getHidden());
-        }
-
-        // Locked by 'this'
-        private void buildLastAudiblyAlertedLocked() {
-            mLastAudiblyAlerted = buildLongMapFromBundle(mRankingUpdate.getLastAudiblyAlerted());
-        }
-
-        // Locked by 'this'
-        private void buildNoisyLocked() {
-            mNoisy = buildBooleanMapFromBundle(mRankingUpdate.getNoisy());
-        }
-
-        // Locked by 'this'
-        private void buildSmartActions() {
-            Bundle smartActions = mRankingUpdate.getSmartActions();
-            mSmartActions = new ArrayMap<>(smartActions.size());
-            for (String key : smartActions.keySet()) {
-                mSmartActions.put(key, smartActions.getParcelableArrayList(key));
-            }
-        }
-
-        // Locked by 'this'
-        private void buildSmartReplies() {
-            Bundle smartReplies = mRankingUpdate.getSmartReplies();
-            mSmartReplies = new ArrayMap<>(smartReplies.size());
-            for (String key : smartReplies.keySet()) {
-                mSmartReplies.put(key, smartReplies.getCharSequenceArrayList(key));
-            }
-        }
-
-        // ----------- Parcelable
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeParcelable(mRankingUpdate, flags);
-        }
-
-        public static final @android.annotation.NonNull Creator<RankingMap> CREATOR = new Creator<RankingMap>() {
-            @Override
-            public RankingMap createFromParcel(Parcel source) {
-                NotificationRankingUpdate rankingUpdate = source.readParcelable(null);
-                return new RankingMap(rankingUpdate);
-            }
-
-            @Override
-            public RankingMap[] newArray(int size) {
-                return new RankingMap[size];
-            }
-        };
     }
 
     private final class MyHandler extends Handler {
diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java
index c5c70f8..675c5cd 100644
--- a/core/java/android/service/notification/NotificationRankingUpdate.java
+++ b/core/java/android/service/notification/NotificationRankingUpdate.java
@@ -15,7 +15,6 @@
  */
 package android.service.notification;
 
-import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,73 +22,18 @@
  * @hide
  */
 public class NotificationRankingUpdate implements Parcelable {
-    // TODO: Support incremental updates.
-    private final String[] mKeys;
-    private final String[] mInterceptedKeys;
-    private final Bundle mVisibilityOverrides;
-    private final Bundle mSuppressedVisualEffects;
-    private final int[] mImportance;
-    private final Bundle mImportanceExplanation;
-    private final Bundle mOverrideGroupKeys;
-    private final Bundle mChannels;
-    private final Bundle mOverridePeople;
-    private final Bundle mSnoozeCriteria;
-    private final Bundle mShowBadge;
-    private final Bundle mUserSentiment;
-    private final Bundle mHidden;
-    private final Bundle mSmartActions;
-    private final Bundle mSmartReplies;
-    private final Bundle mLastAudiblyAlerted;
-    private final Bundle mNoisy;
-    private final boolean[] mCanBubble;
+    private final NotificationListenerService.RankingMap mRankingMap;
 
-    public NotificationRankingUpdate(String[] keys, String[] interceptedKeys,
-            Bundle visibilityOverrides, Bundle suppressedVisualEffects,
-            int[] importance, Bundle explanation, Bundle overrideGroupKeys,
-            Bundle channels, Bundle overridePeople, Bundle snoozeCriteria,
-            Bundle showBadge, Bundle userSentiment, Bundle hidden, Bundle smartActions,
-            Bundle smartReplies, Bundle lastAudiblyAlerted, Bundle noisy, boolean[] canBubble) {
-        mKeys = keys;
-        mInterceptedKeys = interceptedKeys;
-        mVisibilityOverrides = visibilityOverrides;
-        mSuppressedVisualEffects = suppressedVisualEffects;
-        mImportance = importance;
-        mImportanceExplanation = explanation;
-        mOverrideGroupKeys = overrideGroupKeys;
-        mChannels = channels;
-        mOverridePeople = overridePeople;
-        mSnoozeCriteria = snoozeCriteria;
-        mShowBadge = showBadge;
-        mUserSentiment = userSentiment;
-        mHidden = hidden;
-        mSmartActions = smartActions;
-        mSmartReplies = smartReplies;
-        mLastAudiblyAlerted = lastAudiblyAlerted;
-        mNoisy = noisy;
-        mCanBubble = canBubble;
+    public NotificationRankingUpdate(NotificationListenerService.Ranking[] rankings) {
+        mRankingMap = new NotificationListenerService.RankingMap(rankings);
     }
 
     public NotificationRankingUpdate(Parcel in) {
-        mKeys = in.readStringArray();
-        mInterceptedKeys = in.readStringArray();
-        mVisibilityOverrides = in.readBundle();
-        mSuppressedVisualEffects = in.readBundle();
-        mImportance = new int[mKeys.length];
-        in.readIntArray(mImportance);
-        mImportanceExplanation = in.readBundle();
-        mOverrideGroupKeys = in.readBundle();
-        mChannels = in.readBundle();
-        mOverridePeople = in.readBundle();
-        mSnoozeCriteria = in.readBundle();
-        mShowBadge = in.readBundle();
-        mUserSentiment = in.readBundle();
-        mHidden = in.readBundle();
-        mSmartActions = in.readBundle();
-        mSmartReplies = in.readBundle();
-        mLastAudiblyAlerted = in.readBundle();
-        mNoisy = in.readBundle();
-        mCanBubble = new boolean[mKeys.length];
-        in.readBooleanArray(mCanBubble);
+        mRankingMap = in.readParcelable(getClass().getClassLoader());
+    }
+
+    public NotificationListenerService.RankingMap getRankingMap() {
+        return mRankingMap;
     }
 
     @Override
@@ -98,25 +42,17 @@
     }
 
     @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+
+        NotificationRankingUpdate other = (NotificationRankingUpdate) o;
+        return mRankingMap.equals(other.mRankingMap);
+    }
+
+    @Override
     public void writeToParcel(Parcel out, int flags) {
-        out.writeStringArray(mKeys);
-        out.writeStringArray(mInterceptedKeys);
-        out.writeBundle(mVisibilityOverrides);
-        out.writeBundle(mSuppressedVisualEffects);
-        out.writeIntArray(mImportance);
-        out.writeBundle(mImportanceExplanation);
-        out.writeBundle(mOverrideGroupKeys);
-        out.writeBundle(mChannels);
-        out.writeBundle(mOverridePeople);
-        out.writeBundle(mSnoozeCriteria);
-        out.writeBundle(mShowBadge);
-        out.writeBundle(mUserSentiment);
-        out.writeBundle(mHidden);
-        out.writeBundle(mSmartActions);
-        out.writeBundle(mSmartReplies);
-        out.writeBundle(mLastAudiblyAlerted);
-        out.writeBundle(mNoisy);
-        out.writeBooleanArray(mCanBubble);
+        out.writeParcelable(mRankingMap, flags);
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<NotificationRankingUpdate> CREATOR
@@ -129,76 +65,4 @@
             return new NotificationRankingUpdate[size];
         }
     };
-
-    public String[] getOrderedKeys() {
-        return mKeys;
-    }
-
-    public String[] getInterceptedKeys() {
-        return mInterceptedKeys;
-    }
-
-    public Bundle getVisibilityOverrides() {
-        return mVisibilityOverrides;
-    }
-
-    public Bundle getSuppressedVisualEffects() {
-        return mSuppressedVisualEffects;
-    }
-
-    public int[] getImportance() {
-        return mImportance;
-    }
-
-    public Bundle getImportanceExplanation() {
-        return mImportanceExplanation;
-    }
-
-    public Bundle getOverrideGroupKeys() {
-        return mOverrideGroupKeys;
-    }
-
-    public Bundle getChannels() {
-        return mChannels;
-    }
-
-    public Bundle getOverridePeople() {
-        return mOverridePeople;
-    }
-
-    public Bundle getSnoozeCriteria() {
-        return mSnoozeCriteria;
-    }
-
-    public Bundle getShowBadge() {
-        return mShowBadge;
-    }
-
-    public Bundle getUserSentiment() {
-        return mUserSentiment;
-    }
-
-    public Bundle getHidden() {
-        return mHidden;
-    }
-
-    public Bundle getSmartActions() {
-        return mSmartActions;
-    }
-
-    public Bundle getSmartReplies() {
-        return mSmartReplies;
-    }
-
-    public Bundle getLastAudiblyAlerted() {
-        return mLastAudiblyAlerted;
-    }
-
-    public Bundle getNoisy() {
-        return mNoisy;
-    }
-
-    public boolean[] getCanBubble() {
-        return mCanBubble;
-    }
 }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8485f46..82b16de 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -7234,72 +7234,42 @@
     @GuardedBy("mNotificationLock")
     private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
         final int N = mNotificationList.size();
-        ArrayList<String> keys = new ArrayList<String>(N);
-        ArrayList<String> interceptedKeys = new ArrayList<String>(N);
-        ArrayList<Integer> importance = new ArrayList<>(N);
-        Bundle overrideGroupKeys = new Bundle();
-        Bundle visibilityOverrides = new Bundle();
-        Bundle suppressedVisualEffects = new Bundle();
-        Bundle explanation = new Bundle();
-        Bundle channels = new Bundle();
-        Bundle overridePeople = new Bundle();
-        Bundle snoozeCriteria = new Bundle();
-        Bundle showBadge = new Bundle();
-        Bundle userSentiment = new Bundle();
-        Bundle hidden = new Bundle();
-        Bundle systemGeneratedSmartActions = new Bundle();
-        Bundle smartReplies = new Bundle();
-        Bundle lastAudiblyAlerted = new Bundle();
-        Bundle noisy = new Bundle();
-        ArrayList<Boolean> canBubble = new ArrayList<>(N);
+        final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>();
+
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
                 continue;
             }
             final String key = record.sbn.getKey();
-            keys.add(key);
-            importance.add(record.getImportance());
-            if (record.getImportanceExplanation() != null) {
-                explanation.putCharSequence(key, record.getImportanceExplanation());
-            }
-            if (record.isIntercepted()) {
-                interceptedKeys.add(key);
+            final NotificationListenerService.Ranking ranking =
+                    new NotificationListenerService.Ranking();
+            ranking.populate(
+                    key,
+                    rankings.size(),
+                    !record.isIntercepted(),
+                    record.getPackageVisibilityOverride(),
+                    record.getSuppressedVisualEffects(),
+                    record.getImportance(),
+                    record.getImportanceExplanation(),
+                    record.sbn.getOverrideGroupKey(),
+                    record.getChannel(),
+                    record.getPeopleOverride(),
+                    record.getSnoozeCriteria(),
+                    record.canShowBadge(),
+                    record.getUserSentiment(),
+                    record.isHidden(),
+                    record.getLastAudiblyAlertedMs(),
+                    record.getSound() != null || record.getVibration() != null,
+                    record.getSystemGeneratedSmartActions(),
+                    record.getSmartReplies(),
+                    record.canBubble()
+            );
+            rankings.add(ranking);
+        }
 
-            }
-            suppressedVisualEffects.putInt(key, record.getSuppressedVisualEffects());
-            if (record.getPackageVisibilityOverride()
-                    != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
-                visibilityOverrides.putInt(key, record.getPackageVisibilityOverride());
-            }
-            overrideGroupKeys.putString(key, record.sbn.getOverrideGroupKey());
-            channels.putParcelable(key, record.getChannel());
-            overridePeople.putStringArrayList(key, record.getPeopleOverride());
-            snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
-            showBadge.putBoolean(key, record.canShowBadge());
-            userSentiment.putInt(key, record.getUserSentiment());
-            hidden.putBoolean(key, record.isHidden());
-            systemGeneratedSmartActions.putParcelableArrayList(key,
-                    record.getSystemGeneratedSmartActions());
-            smartReplies.putCharSequenceArrayList(key, record.getSmartReplies());
-            lastAudiblyAlerted.putLong(key, record.getLastAudiblyAlertedMs());
-            noisy.putBoolean(key, record.getSound() != null || record.getVibration() != null);
-            canBubble.add(record.canBubble());
-        }
-        final int M = keys.size();
-        String[] keysAr = keys.toArray(new String[M]);
-        String[] interceptedKeysAr = interceptedKeys.toArray(new String[interceptedKeys.size()]);
-        int[] importanceAr = new int[M];
-        boolean[] canBubbleAr = new boolean[M];
-        for (int i = 0; i < M; i++) {
-            importanceAr[i] = importance.get(i);
-            canBubbleAr[i] = canBubble.get(i);
-        }
-        return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
-                suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge, userSentiment, hidden,
-                systemGeneratedSmartActions, smartReplies, lastAudiblyAlerted, noisy,
-                canBubbleAr);
+        return new NotificationRankingUpdate(
+                rankings.toArray(new NotificationListenerService.Ranking[0]));
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
index 52c199a3..bee3b2b 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java
@@ -20,7 +20,9 @@
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
 import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -33,10 +35,11 @@
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.IBinder;
+import android.os.Parcel;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.NotificationListenerService.RankingMap;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.SnoozeCriterion;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -55,8 +58,6 @@
 @RunWith(AndroidJUnit4.class)
 public class NotificationListenerServiceTest extends UiServiceTestCase {
 
-    private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
-
     @Test
     public void testGetActiveNotifications_notNull() throws Exception {
         TestListenerService service = new TestListenerService();
@@ -97,52 +98,144 @@
         }
     }
 
-    private NotificationRankingUpdate generateUpdate() {
-        List<String> interceptedKeys = new ArrayList<>();
-        Bundle visibilityOverrides = new Bundle();
-        Bundle overrideGroupKeys = new Bundle();
-        Bundle suppressedVisualEffects = new Bundle();
-        Bundle explanation = new Bundle();
-        Bundle channels = new Bundle();
-        Bundle overridePeople = new Bundle();
-        Bundle snoozeCriteria = new Bundle();
-        Bundle showBadge = new Bundle();
-        int[] importance = new int[mKeys.length];
-        Bundle userSentiment = new Bundle();
-        Bundle mHidden = new Bundle();
-        Bundle smartActions = new Bundle();
-        Bundle smartReplies = new Bundle();
-        Bundle lastAudiblyAlerted = new Bundle();
-        Bundle noisy = new Bundle();
-        boolean[] canBubble = new boolean[mKeys.length];
+    // Tests parceling of NotificationRankingUpdate, and by extension, RankingMap and Ranking.
+    @Test
+    public void testRankingUpdate_parcel() {
+        NotificationRankingUpdate nru = generateUpdate();
+        Parcel parcel = Parcel.obtain();
+        nru.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        NotificationRankingUpdate nru1 = NotificationRankingUpdate.CREATOR.createFromParcel(parcel);
+        assertEquals(nru, nru1);
+    }
 
-        for (int i = 0; i < mKeys.length; i++) {
-            String key = mKeys[i];
-            visibilityOverrides.putInt(key, getVisibilityOverride(i));
-            overrideGroupKeys.putString(key, getOverrideGroupKey(key));
-            if (isIntercepted(i)) {
-                interceptedKeys.add(key);
-            }
-            suppressedVisualEffects.putInt(key, getSuppressedVisualEffects(i));
-            importance[i] = getImportance(i);
-            explanation.putString(key, getExplanation(key));
-            channels.putParcelable(key, getChannel(key, i));
-            overridePeople.putStringArrayList(key, getPeople(key, i));
-            snoozeCriteria.putParcelableArrayList(key, getSnoozeCriteria(key, i));
-            showBadge.putBoolean(key, getShowBadge(i));
-            userSentiment.putInt(key, getUserSentiment(i));
-            mHidden.putBoolean(key, getHidden(i));
-            smartActions.putParcelableArrayList(key, getSmartActions(key, i));
-            smartReplies.putCharSequenceArrayList(key, getSmartReplies(key, i));
-            lastAudiblyAlerted.putLong(key, lastAudiblyAlerted(i));
-            noisy.putBoolean(key, getNoisy(i));
-            canBubble[i] = canBubble(i);
+    private void detailedAssertEquals(RankingMap a, RankingMap b) {
+        Ranking arank = new Ranking();
+        Ranking brank = new Ranking();
+        assertArrayEquals(a.getOrderedKeys(), b.getOrderedKeys());
+        for (String key : a.getOrderedKeys()) {
+            a.getRanking(key, arank);
+            b.getRanking(key, brank);
+            detailedAssertEquals("ranking for key <" + key + ">", arank, brank);
         }
-        NotificationRankingUpdate update = new NotificationRankingUpdate(mKeys,
-                interceptedKeys.toArray(new String[0]), visibilityOverrides,
-                suppressedVisualEffects, importance, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden,
-                smartActions, smartReplies, lastAudiblyAlerted, noisy, canBubble);
+    }
+
+    // Tests parceling of RankingMap and RankingMap.equals
+    @Test
+    public void testRankingMap_parcel() {
+        RankingMap rmap = generateUpdate().getRankingMap();
+        Parcel parcel = Parcel.obtain();
+        rmap.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        RankingMap rmap1 = RankingMap.CREATOR.createFromParcel(parcel);
+
+        detailedAssertEquals(rmap, rmap1);
+        assertEquals(rmap, rmap1);
+    }
+
+    private void detailedAssertEquals(String comment, Ranking a, Ranking b) {
+        assertEquals(comment, a.getKey(), b.getKey());
+        assertEquals(comment, a.getRank(), b.getRank());
+        assertEquals(comment, a.matchesInterruptionFilter(), b.matchesInterruptionFilter());
+        assertEquals(comment, a.getVisibilityOverride(), b.getVisibilityOverride());
+        assertEquals(comment, a.getSuppressedVisualEffects(), b.getSuppressedVisualEffects());
+        assertEquals(comment, a.getImportance(), b.getImportance());
+        assertEquals(comment, a.getImportanceExplanation(), b.getImportanceExplanation());
+        assertEquals(comment, a.getOverrideGroupKey(), b.getOverrideGroupKey());
+        assertEquals(comment, a.getChannel(), b.getChannel());
+        assertEquals(comment, a.getAdditionalPeople(), b.getAdditionalPeople());
+        assertEquals(comment, a.getSnoozeCriteria(), b.getSnoozeCriteria());
+        assertEquals(comment, a.canShowBadge(), b.canShowBadge());
+        assertEquals(comment, a.getUserSentiment(), b.getUserSentiment());
+        assertEquals(comment, a.isSuspended(), b.isSuspended());
+        assertEquals(comment, a.getLastAudiblyAlertedMillis(), b.getLastAudiblyAlertedMillis());
+        assertEquals(comment, a.isNoisy(), b.isNoisy());
+        assertEquals(comment, a.getSmartReplies(), b.getSmartReplies());
+        assertEquals(comment, a.canBubble(), b.canBubble());
+        assertActionsEqual(a.getSmartActions(), b.getSmartActions());
+    }
+
+    // Tests parceling of Ranking and Ranking.equals
+    @Test
+    public void testRanking_parcel() {
+        Ranking ranking = generateUpdate().getRankingMap().getRawRankingObject(mKeys[0]);
+        Parcel parcel = Parcel.obtain();
+        ranking.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+        Ranking ranking1 = new Ranking(parcel);
+        detailedAssertEquals("rankings differ: ", ranking, ranking1);
+        assertEquals(ranking, ranking1);
+    }
+
+    private void detailedAssertEquals(NotificationRankingUpdate a, NotificationRankingUpdate b) {
+        assertEquals(a.getRankingMap(), b.getRankingMap());
+    }
+
+    // Tests NotificationRankingUpdate.equals(), and by extension, RankingMap and Ranking.
+    @Test
+    public void testRankingUpdate_equals() {
+        NotificationRankingUpdate nru = generateUpdate();
+        NotificationRankingUpdate nru2 = generateUpdate();
+        detailedAssertEquals(nru, nru2);
+        assertEquals(nru, nru2);
+        Ranking tweak = nru2.getRankingMap().getRawRankingObject(mKeys[0]);
+        tweak.populate(
+                tweak.getKey(),
+                tweak.getRank(),
+                !tweak.matchesInterruptionFilter(), // note the inversion here!
+                tweak.getVisibilityOverride(),
+                tweak.getSuppressedVisualEffects(),
+                tweak.getImportance(),
+                tweak.getImportanceExplanation(),
+                tweak.getOverrideGroupKey(),
+                tweak.getChannel(),
+                (ArrayList) tweak.getAdditionalPeople(),
+                (ArrayList) tweak.getSnoozeCriteria(),
+                tweak.canShowBadge(),
+                tweak.getUserSentiment(),
+                tweak.isSuspended(),
+                tweak.getLastAudiblyAlertedMillis(),
+                tweak.isNoisy(),
+                (ArrayList) tweak.getSmartActions(),
+                (ArrayList) tweak.getSmartReplies(),
+                tweak.canBubble()
+        );
+        assertNotEquals(nru, nru2);
+    }
+
+    // Test data
+
+    private String[] mKeys = new String[] { "key", "key1", "key2", "key3", "key4"};
+
+    private NotificationRankingUpdate generateUpdate() {
+        Ranking[] rankings = new Ranking[mKeys.length];
+        for (int i = 0; i < mKeys.length; i++) {
+            final String key = mKeys[i];
+            Ranking ranking = new Ranking();
+            ranking.populate(
+                    key,
+                    i,
+                    !isIntercepted(i),
+                    getVisibilityOverride(i),
+                    getSuppressedVisualEffects(i),
+                    getImportance(i),
+                    getExplanation(key),
+                    getOverrideGroupKey(key),
+                    getChannel(key, i),
+                    getPeople(key, i),
+                    getSnoozeCriteria(key, i),
+                    getShowBadge(i),
+                    getUserSentiment(i),
+                    getHidden(i),
+                    lastAudiblyAlerted(i),
+                    getNoisy(i),
+                    getSmartActions(key, i),
+                    getSmartReplies(key, i),
+                    canBubble(i)
+            );
+            rankings[i] = ranking;
+        }
+        NotificationRankingUpdate update = new NotificationRankingUpdate(rankings);
         return update;
     }