Merge "HUN: Implement per-package config."
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 5d864df..33262b3 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -48,6 +48,9 @@
     void setPackagePriority(String pkg, int uid, int priority);
     int getPackagePriority(String pkg, int uid);
 
+    void setPackagePeekable(String pkg, int uid, boolean peekable);
+    boolean getPackagePeekable(String pkg, int uid);
+
     void setPackageVisibilityOverride(String pkg, int uid, int visibility);
     int getPackageVisibilityOverride(String pkg, int uid);
 
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index b824e97..b31ce04 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1840,6 +1840,26 @@
     }
 
     /**
+     * {@hide}
+     */
+    public static String priorityToString(@Priority int pri) {
+        switch (pri) {
+            case PRIORITY_MIN:
+                return "MIN";
+            case PRIORITY_LOW:
+                return "LOW";
+            case PRIORITY_DEFAULT:
+                return "DEFAULT";
+            case PRIORITY_HIGH:
+                return "HIGH";
+            case PRIORITY_MAX:
+                return "MAX";
+            default:
+                return "UNKNOWN(" + String.valueOf(pri) + ")";
+        }
+    }
+
+    /**
      * @hide
      */
     public boolean isValid() {
diff --git a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
index 1335706..22cdd58 100644
--- a/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationIntrusivenessExtractor.java
@@ -18,6 +18,7 @@
 
 import android.app.Notification;
 import android.content.Context;
+import android.util.Log;
 import android.util.Slog;
 
 /**
@@ -25,8 +26,8 @@
  * notifications and marks them to get a temporary ranking bump.
  */
 public class NotificationIntrusivenessExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "NotificationNoiseExtractor";
-    private static final boolean DBG = false;
+    private static final String TAG = "IntrusivenessExtractor";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
 
     /** Length of time (in milliseconds) that an intrusive or noisy notification will stay at
     the top of the ranking order, before it falls back to its natural position. */
@@ -48,7 +49,7 @@
                 (notification.defaults & Notification.DEFAULT_SOUND) != 0 ||
                 notification.sound != null ||
                 notification.fullScreenIntent != null) {
-            record.setRecentlyIntusive(true);
+            record.setRecentlyIntrusive(true);
         }
 
         return new RankingReconsideration(record.getKey(), HANG_TIME_MS) {
@@ -59,7 +60,7 @@
 
             @Override
             public void applyChangesLocked(NotificationRecord record) {
-                record.setRecentlyIntusive(false);
+                record.setRecentlyIntrusive(false);
             }
         };
     }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0c71d5f..c330046 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -1216,6 +1216,19 @@
         }
 
         @Override
+        public void setPackagePeekable(String pkg, int uid, boolean peekable) {
+            checkCallerIsSystem();
+
+            mRankingHelper.setPackagePeekable(pkg, uid, peekable);
+        }
+
+        @Override
+        public boolean getPackagePeekable(String pkg, int uid) {
+            checkCallerIsSystem();
+            return mRankingHelper.getPackagePeekable(pkg, uid);
+        }
+
+        @Override
         public void setPackageVisibilityOverride(String pkg, int uid, int visibility) {
             checkCallerIsSystem();
             mRankingHelper.setPackageVisibilityOverride(pkg, uid, visibility);
@@ -1845,6 +1858,14 @@
                             notification.priority = Notification.PRIORITY_HIGH;
                         }
                     }
+                    // force no heads up per package config
+                    if (!mRankingHelper.getPackagePeekable(pkg, callingUid)) {
+                        if (notification.extras == null) {
+                            notification.extras = new Bundle();
+                        }
+                        notification.extras.putInt(Notification.EXTRA_AS_HEADS_UP,
+                                Notification.HEADS_UP_NEVER);
+                    }
 
                     // 1. initial score: buckets of 10, around the app [-20..20]
                     final int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER;
diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java
index ea6f2db..39fd9ab 100644
--- a/services/core/java/com/android/server/notification/NotificationRecord.java
+++ b/services/core/java/com/android/server/notification/NotificationRecord.java
@@ -209,7 +209,7 @@
         return mContactAffinity;
     }
 
-    public void setRecentlyIntusive(boolean recentlyIntrusive) {
+    public void setRecentlyIntrusive(boolean recentlyIntrusive) {
         mRecentlyIntrusive = recentlyIntrusive;
     }
 
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index aea137b..803db10 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -20,6 +20,10 @@
 
     void setPackagePriority(String packageName, int uid, int priority);
 
+    boolean getPackagePeekable(String packageName, int uid);
+
+    void setPackagePeekable(String packageName, int uid, boolean peekable);
+
     int getPackageVisibilityOverride(String packageName, int uid);
 
     void setPackageVisibilityOverride(String packageName, int uid, int visibility);
diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index 518e223..88055ba 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/com/android/server/notification/RankingHelper.java
@@ -23,9 +23,8 @@
 import android.service.notification.NotificationListenerService;
 import android.text.TextUtils;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.Slog;
-import android.util.SparseIntArray;
+
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -34,12 +33,10 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 public class RankingHelper implements RankingConfig {
     private static final String TAG = "RankingHelper";
-    private static final boolean DEBUG = false;
 
     private static final int XML_VERSION = 1;
 
@@ -50,16 +47,20 @@
     private static final String ATT_NAME = "name";
     private static final String ATT_UID = "uid";
     private static final String ATT_PRIORITY = "priority";
+    private static final String ATT_PEEKABLE = "peekable";
     private static final String ATT_VISIBILITY = "visibility";
 
+    private static final int DEFAULT_PRIORITY = Notification.PRIORITY_DEFAULT;
+    private static final boolean DEFAULT_PEEKABLE = true;
+    private static final int DEFAULT_VISIBILITY =
+            NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+
     private final NotificationSignalExtractor[] mSignalExtractors;
     private final NotificationComparator mPreliminaryComparator = new NotificationComparator();
     private final GlobalSortKeyComparator mFinalComparator = new GlobalSortKeyComparator();
 
-    // Package name to uid, to priority. Would be better as Table<String, Int, Int>
-    private final ArrayMap<String, SparseIntArray> mPackagePriorities;
-    private final ArrayMap<String, SparseIntArray> mPackageVisibilities;
-    private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp;
+    private final ArrayMap<String, Record> mRecords = new ArrayMap<>(); // pkg|uid => Record
+    private final ArrayMap<String, NotificationRecord> mProxyByGroupTmp = new ArrayMap<>();
 
     private final Context mContext;
     private final Handler mRankingHandler;
@@ -67,8 +68,6 @@
     public RankingHelper(Context context, Handler rankingHandler, String[] extractorNames) {
         mContext = context;
         mRankingHandler = rankingHandler;
-        mPackagePriorities = new ArrayMap<String, SparseIntArray>();
-        mPackageVisibilities = new ArrayMap<String, SparseIntArray>();
 
         final int N = extractorNames.length;
         mSignalExtractors = new NotificationSignalExtractor[N];
@@ -88,9 +87,9 @@
                 Slog.w(TAG, "Problem accessing extractor " + extractorNames[i] + ".", e);
             }
         }
-        mProxyByGroupTmp = new ArrayMap<String, NotificationRecord>();
     }
 
+    @SuppressWarnings("unchecked")
     public <T extends NotificationSignalExtractor> T findExtractor(Class<T> extractorClass) {
         final int N = mSignalExtractors.length;
         for (int i = 0; i < N; i++) {
@@ -125,8 +124,7 @@
         if (type != XmlPullParser.START_TAG) return;
         String tag = parser.getName();
         if (!TAG_RANKING.equals(tag)) return;
-        mPackagePriorities.clear();
-        final int version = safeInt(parser, ATT_VERSION, XML_VERSION);
+        mRecords.clear();
         while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
             tag = parser.getName();
             if (type == XmlPullParser.END_TAG && TAG_RANKING.equals(tag)) {
@@ -135,27 +133,20 @@
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_PACKAGE.equals(tag)) {
                     int uid = safeInt(parser, ATT_UID, UserHandle.USER_ALL);
-                    int priority = safeInt(parser, ATT_PRIORITY, Notification.PRIORITY_DEFAULT);
-                    int vis = safeInt(parser, ATT_VISIBILITY,
-                            NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
+                    int priority = safeInt(parser, ATT_PRIORITY, DEFAULT_PRIORITY);
+                    boolean peekable = safeBool(parser, ATT_PEEKABLE, DEFAULT_PEEKABLE);
+                    int vis = safeInt(parser, ATT_VISIBILITY, DEFAULT_VISIBILITY);
                     String name = parser.getAttributeValue(null, ATT_NAME);
 
                     if (!TextUtils.isEmpty(name)) {
-                        if (priority != Notification.PRIORITY_DEFAULT) {
-                            SparseIntArray priorityByUid = mPackagePriorities.get(name);
-                            if (priorityByUid == null) {
-                                priorityByUid = new SparseIntArray();
-                                mPackagePriorities.put(name, priorityByUid);
-                            }
-                            priorityByUid.put(uid, priority);
+                        if (priority != DEFAULT_PRIORITY) {
+                            getOrCreateRecord(name, uid).priority = priority;
                         }
-                        if (vis != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
-                            SparseIntArray visibilityByUid = mPackageVisibilities.get(name);
-                            if (visibilityByUid == null) {
-                                visibilityByUid = new SparseIntArray();
-                                mPackageVisibilities.put(name, visibilityByUid);
-                            }
-                            visibilityByUid.put(uid, vis);
+                        if (peekable != DEFAULT_PEEKABLE) {
+                            getOrCreateRecord(name, uid).peekable = peekable;
+                        }
+                        if (vis != DEFAULT_VISIBILITY) {
+                            getOrCreateRecord(name, uid).visibility = vis;
                         }
                     }
                 }
@@ -164,49 +155,53 @@
         throw new IllegalStateException("Failed to reach END_DOCUMENT");
     }
 
+    private static String recordKey(String pkg, int uid) {
+        return pkg + "|" + uid;
+    }
+
+    private Record getOrCreateRecord(String pkg, int uid) {
+        final String key = recordKey(pkg, uid);
+        Record r = mRecords.get(key);
+        if (r == null) {
+            r = new Record();
+            r.pkg = pkg;
+            r.uid = uid;
+            mRecords.put(key, r);
+        }
+        return r;
+    }
+
+    private void removeDefaultRecords() {
+        final int N = mRecords.size();
+        for (int i = N - 1; i >= 0; i--) {
+            final Record r = mRecords.valueAt(i);
+            if (r.priority == DEFAULT_PRIORITY && r.peekable == DEFAULT_PEEKABLE
+                    && r.visibility == DEFAULT_VISIBILITY) {
+                mRecords.remove(i);
+            }
+        }
+    }
+
     public void writeXml(XmlSerializer out) throws IOException {
         out.startTag(null, TAG_RANKING);
         out.attribute(null, ATT_VERSION, Integer.toString(XML_VERSION));
 
-        final Set<String> packageNames = new ArraySet<>(mPackagePriorities.size()
-                + mPackageVisibilities.size());
-        packageNames.addAll(mPackagePriorities.keySet());
-        packageNames.addAll(mPackageVisibilities.keySet());
-        final Set<Integer> packageUids = new ArraySet<>();
-        for (String packageName : packageNames) {
-            packageUids.clear();
-            SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
-            SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
-            if (priorityByUid != null) {
-                final int M = priorityByUid.size();
-                for (int j = 0; j < M; j++) {
-                    packageUids.add(priorityByUid.keyAt(j));
-                }
+        final int N = mRecords.size();
+        for (int i = 0; i < N; i++) {
+            final Record r = mRecords.valueAt(i);
+            out.startTag(null, TAG_PACKAGE);
+            out.attribute(null, ATT_NAME, r.pkg);
+            if (r.priority != DEFAULT_PRIORITY) {
+                out.attribute(null, ATT_PRIORITY, Integer.toString(r.priority));
             }
-            if (visibilityByUid != null) {
-                final int M = visibilityByUid.size();
-                for (int j = 0; j < M; j++) {
-                    packageUids.add(visibilityByUid.keyAt(j));
-                }
+            if (r.peekable != DEFAULT_PEEKABLE) {
+                out.attribute(null, ATT_PEEKABLE, Boolean.toString(r.peekable));
             }
-            for (Integer uid : packageUids) {
-                out.startTag(null, TAG_PACKAGE);
-                out.attribute(null, ATT_NAME, packageName);
-                if (priorityByUid != null) {
-                    final int priority = priorityByUid.get(uid);
-                    if (priority != Notification.PRIORITY_DEFAULT) {
-                        out.attribute(null, ATT_PRIORITY, Integer.toString(priority));
-                    }
-                }
-                if (visibilityByUid != null) {
-                    final int visibility = visibilityByUid.get(uid);
-                    if (visibility != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE) {
-                        out.attribute(null, ATT_VISIBILITY, Integer.toString(visibility));
-                    }
-                }
-                out.attribute(null, ATT_UID, Integer.toString(uid));
-                out.endTag(null, TAG_PACKAGE);
+            if (r.visibility != DEFAULT_VISIBILITY) {
+                out.attribute(null, ATT_VISIBILITY, Integer.toString(r.visibility));
             }
+            out.attribute(null, ATT_UID, Integer.toString(r.uid));
+            out.endTag(null, TAG_PACKAGE);
         }
         out.endTag(null, TAG_RANKING);
     }
@@ -295,14 +290,20 @@
         }
     }
 
+    private static boolean safeBool(XmlPullParser parser, String att, boolean defValue) {
+        final String val = parser.getAttributeValue(null, att);
+        return tryParseBool(val, defValue);
+    }
+
+    private static boolean tryParseBool(String value, boolean defValue) {
+        if (TextUtils.isEmpty(value)) return defValue;
+        return Boolean.valueOf(value);
+    }
+
     @Override
     public int getPackagePriority(String packageName, int uid) {
-        int priority = Notification.PRIORITY_DEFAULT;
-        SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
-        if (priorityByUid != null) {
-            priority = priorityByUid.get(uid, Notification.PRIORITY_DEFAULT);
-        }
-        return priority;
+        final Record r = mRecords.get(recordKey(packageName, uid));
+        return r != null ? r.priority : DEFAULT_PRIORITY;
     }
 
     @Override
@@ -310,24 +311,31 @@
         if (priority == getPackagePriority(packageName, uid)) {
             return;
         }
-        SparseIntArray priorityByUid = mPackagePriorities.get(packageName);
-        if (priorityByUid == null) {
-            priorityByUid = new SparseIntArray();
-            mPackagePriorities.put(packageName, priorityByUid);
+        getOrCreateRecord(packageName, uid).priority = priority;
+        removeDefaultRecords();
+        updateConfig();
+    }
+
+    @Override
+    public boolean getPackagePeekable(String packageName, int uid) {
+        final Record r = mRecords.get(recordKey(packageName, uid));
+        return r != null ? r.peekable : DEFAULT_PEEKABLE;
+    }
+
+    @Override
+    public void setPackagePeekable(String packageName, int uid, boolean peekable) {
+        if (peekable == getPackagePeekable(packageName, uid)) {
+            return;
         }
-        priorityByUid.put(uid, priority);
+        getOrCreateRecord(packageName, uid).peekable = peekable;
+        removeDefaultRecords();
         updateConfig();
     }
 
     @Override
     public int getPackageVisibilityOverride(String packageName, int uid) {
-        int visibility = NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
-        SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
-        if (visibilityByUid != null) {
-            visibility = visibilityByUid.get(uid,
-                    NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE);
-        }
-        return visibility;
+        final Record r = mRecords.get(recordKey(packageName, uid));
+        return r != null ? r.visibility : DEFAULT_VISIBILITY;
     }
 
     @Override
@@ -335,12 +343,8 @@
         if (visibility == getPackageVisibilityOverride(packageName, uid)) {
             return;
         }
-        SparseIntArray visibilityByUid = mPackageVisibilities.get(packageName);
-        if (visibilityByUid == null) {
-            visibilityByUid = new SparseIntArray();
-            mPackageVisibilities.put(packageName, visibilityByUid);
-        }
-        visibilityByUid.put(uid, visibility);
+        getOrCreateRecord(packageName, uid).visibility = visibility;
+        removeDefaultRecords();
         updateConfig();
     }
 
@@ -356,28 +360,42 @@
                 pw.println(mSignalExtractors[i]);
             }
         }
-        final int N = mPackagePriorities.size();
         if (filter == null) {
             pw.print(prefix);
-            pw.println("package priorities:");
+            pw.println("per-package config:");
         }
+        final int N = mRecords.size();
         for (int i = 0; i < N; i++) {
-            String name = mPackagePriorities.keyAt(i);
-            if (filter == null || filter.matches(name)) {
-                SparseIntArray priorityByUid = mPackagePriorities.get(name);
-                final int M = priorityByUid.size();
-                for (int j = 0; j < M; j++) {
-                    int uid = priorityByUid.keyAt(j);
-                    int priority = priorityByUid.get(uid);
-                    pw.print(prefix);
-                    pw.print("  ");
-                    pw.print(name);
-                    pw.print(" (");
-                    pw.print(uid);
-                    pw.print(") has priority: ");
-                    pw.println(priority);
+            final Record r = mRecords.valueAt(i);
+            if (filter == null || filter.matches(r.pkg)) {
+                pw.print(prefix);
+                pw.print("  ");
+                pw.print(r.pkg);
+                pw.print(" (");
+                pw.print(r.uid);
+                pw.print(')');
+                if (r.priority != DEFAULT_PRIORITY) {
+                    pw.print(" priority=");
+                    pw.print(Notification.priorityToString(r.priority));
                 }
+                if (r.peekable != DEFAULT_PEEKABLE) {
+                    pw.print(" peekable=");
+                    pw.print(r.peekable);
+                }
+                if (r.visibility != DEFAULT_VISIBILITY) {
+                    pw.print(" visibility=");
+                    pw.print(Notification.visibilityToString(r.visibility));
+                }
+                pw.println();
             }
         }
     }
+
+    private static class Record {
+        String pkg;
+        int uid;
+        int priority = DEFAULT_PRIORITY;
+        boolean peekable = DEFAULT_PEEKABLE;
+        int visibility = DEFAULT_VISIBILITY;
+    }
 }