diff options
4 files changed, 379 insertions, 648 deletions
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 3ec21e39e514..e02fd9fc5413 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.BitmapDrawable; 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.RemoteException; 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 com.android.internal.os.SomeArgs; 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 @@ public abstract class NotificationListenerService extends Service { */ @GuardedBy("mLock") public final void applyUpdateLocked(NotificationRankingUpdate update) { - mRankingMap = new RankingMap(update); + mRankingMap = update.getRankingMap(); } /** @hide */ @@ -1480,14 +1478,14 @@ public abstract class NotificationListenerService extends Service { */ 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 @@ public abstract class NotificationListenerService extends Service { 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 @@ public abstract class NotificationListenerService extends Service { } /** + * @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 @@ public abstract class NotificationListenerService extends Service { 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,413 +1884,66 @@ public abstract class NotificationListenerService extends Service { * 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 RankingMap(NotificationRankingUpdate rankingUpdate) { - mRankingUpdate = rankingUpdate; - } - - /** - * Request the list of notification keys in their current ranking - * order. - * - * @return An array of active notification keys, in their ranking order. - */ - public String[] getOrderedKeys() { - return mRankingUpdate.getOrderedKeys(); - } + 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<>(); /** - * Populates outRanking with ranking information for the notification - * with the given key. - * - * @return true if a valid key has been passed and outRanking has - * been populated; false otherwise + * @hide */ - 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(); - } - } - Integer rank = mRanks.get(key); - return rank != null ? rank : -1; - } - - private boolean isIntercepted(String key) { - synchronized (this) { - if (mIntercepted == null) { - buildInterceptedSetLocked(); - } - } - return mIntercepted.contains(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)); + 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]); } } - // 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)); - } - } + // -- parcelable interface -- - // 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)); + 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); } } - // 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()); - } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; - // Locked by 'this' - private void buildNoisyLocked() { - mNoisy = buildBooleanMapFromBundle(mRankingUpdate.getNoisy()); - } + RankingMap other = (RankingMap) o; - // 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)); - } - } + return mOrderedKeys.equals(other.mOrderedKeys) + && mRankings.equals(other.mRankings); - // 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 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) { - NotificationRankingUpdate rankingUpdate = source.readParcelable(null); - return new RankingMap(rankingUpdate); + return new RankingMap(source); } @Override @@ -2183,6 +1951,42 @@ public abstract class NotificationListenerService extends Service { return new RankingMap[size]; } }; + + /** + * Request the list of notification keys in their current ranking + * order. + * + * @return An array of active notification keys, in their ranking order. + */ + public String[] getOrderedKeys() { + return mOrderedKeys.toArray(new String[0]); + } + + /** + * Populates outRanking with ranking information for the notification + * with the given key. + * + * @return true if a valid key has been passed and outRanking has + * been populated; false otherwise + */ + public boolean getRanking(String key, Ranking outRanking) { + if (mRankings.containsKey(key)) { + outRanking.populate(mRankings.get(key)); + return true; + } + return false; + } + + /** + * 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 final class MyHandler extends Handler { diff --git a/core/java/android/service/notification/NotificationRankingUpdate.java b/core/java/android/service/notification/NotificationRankingUpdate.java index c5c70f808325..675c5cd63100 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 @@ import android.os.Parcelable; * @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 @@ public class NotificationRankingUpdate implements Parcelable { } @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 @@ public class NotificationRankingUpdate implements Parcelable { 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 8485f46aefd4..82b16dea5b49 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 @@ public class NotificationManagerService extends SystemService { @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); - - } - 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); + 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); + } + + 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 52c199a34f67..bee3b2baf3dd 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.U 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.NotificationChannel; 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 @@ import java.util.List; @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 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { } } - 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); + } + + 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); + } + } + + // 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++) { - 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); + 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(mKeys, - interceptedKeys.toArray(new String[0]), visibilityOverrides, - suppressedVisualEffects, importance, explanation, overrideGroupKeys, - channels, overridePeople, snoozeCriteria, showBadge, userSentiment, mHidden, - smartActions, smartReplies, lastAudiblyAlerted, noisy, canBubble); + NotificationRankingUpdate update = new NotificationRankingUpdate(rankings); return update; } |