Merge "provide a cleaner notification dump" into mnc-dev
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 87e9d42..b23b856 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -112,6 +112,9 @@
 
 import libcore.io.IoUtils;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
@@ -1663,7 +1666,12 @@
                 return;
             }
 
-            dumpImpl(pw, DumpFilter.parseFromArguments(args));
+            final DumpFilter filter = DumpFilter.parseFromArguments(args);
+            if (filter != null && filter.stats) {
+                dumpJson(pw, filter);
+            } else {
+                dumpImpl(pw, filter);
+            }
         }
 
         @Override
@@ -1799,6 +1807,32 @@
             return "callState";
         }
         return null;
+    };
+
+    private void dumpJson(PrintWriter pw, DumpFilter filter) {
+        JSONObject dump = new JSONObject();
+        try {
+            dump.put("service", "Notification Manager");
+            JSONArray bans = new JSONArray();
+            try {
+                ArrayMap<Integer, ArrayList<String>> packageBans = getPackageBans(filter);
+                for (Integer userId : packageBans.keySet()) {
+                    for (String packageName : packageBans.get(userId)) {
+                        JSONObject ban = new JSONObject();
+                        ban.put("userId", userId);
+                        ban.put("packageName", packageName);
+                        bans.put(ban);
+                    }
+                }
+            } catch (NameNotFoundException e) {
+                // pass
+            }
+            dump.put("bans", bans);
+            dump.put("stats", mUsageStats.dumpJson(filter));
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        pw.println(dump);
     }
 
     void dumpImpl(PrintWriter pw, DumpFilter filter) {
@@ -1920,18 +1954,10 @@
 
             try {
                 pw.println("\n  Banned Packages:");
-                for(UserInfo user : UserManager.get(getContext()).getUsers()) {
-                    final int userId = user.getUserHandle().getIdentifier();
-                    pw.println("    UserId " + userId);
-                    final PackageManager packageManager = getContext().getPackageManager();
-                    List<PackageInfo> packages = packageManager.getInstalledPackages(0, userId);
-                    final int packageCount = packages.size();
-                    for (int p = 0; p < packageCount; p++) {
-                        final String packageName = packages.get(p).packageName;
-                        final int uid = packageManager.getPackageUid(packageName, userId);
-                        if (!checkNotificationOp(packageName, uid)) {
-                            pw.println("       " + packageName);
-                        }
+                ArrayMap<Integer, ArrayList<String>> packageBans = getPackageBans(filter);
+                for (Integer userId : packageBans.keySet()) {
+                    for (String packageName : packageBans.get(userId)) {
+                        pw.println("    " + userId + ": " + packageName);
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -1940,6 +1966,32 @@
         }
     }
 
+    private ArrayMap<Integer, ArrayList<String>> getPackageBans(DumpFilter filter)
+            throws NameNotFoundException {
+        ArrayMap<Integer, ArrayList<String>> packageBans = new ArrayMap<>();
+        ArrayList<String> packageNames = new ArrayList<>();
+        for (UserInfo user : UserManager.get(getContext()).getUsers()) {
+            final int userId = user.getUserHandle().getIdentifier();
+            final PackageManager packageManager = getContext().getPackageManager();
+            List<PackageInfo> packages = packageManager.getInstalledPackages(0, userId);
+            final int packageCount = packages.size();
+            for (int p = 0; p < packageCount; p++) {
+                final String packageName = packages.get(p).packageName;
+                if (filter == null || filter.matches(packageName)) {
+                    final int uid = packageManager.getPackageUid(packageName, userId);
+                    if (!checkNotificationOp(packageName, uid)) {
+                        packageNames.add(packageName);
+                    }
+                }
+            }
+            if (!packageNames.isEmpty()) {
+                packageBans.put(userId, packageNames);
+                packageNames = new ArrayList<>();
+            }
+        }
+        return packageBans;
+    }
+
     /**
      * The private API only accessible to the system process.
      */
@@ -3449,6 +3501,9 @@
     public static final class DumpFilter {
         public String pkgFilter;
         public boolean zen;
+        public long since;
+        public boolean stats;
+        private boolean all;
 
         public static DumpFilter parseFromArguments(String[] args) {
             if (args != null && args.length == 2 && "p".equals(args[0])
@@ -3460,27 +3515,35 @@
             if (args != null && args.length == 1 && "zen".equals(args[0])) {
                 final DumpFilter filter = new DumpFilter();
                 filter.zen = true;
+                filter.all = true;
+                return filter;
+            }
+            if (args != null && args.length >= 1 && "--stats".equals(args[0])) {
+                final DumpFilter filter = new DumpFilter();
+                filter.stats = true;
+                filter.since = args.length == 2 ? Long.valueOf(args[1]) : 0;
+                filter.all = true;
                 return filter;
             }
             return null;
         }
 
         public boolean matches(StatusBarNotification sbn) {
-            return zen ? true : sbn != null
+            return all ? true : sbn != null
                     && (matches(sbn.getPackageName()) || matches(sbn.getOpPkg()));
         }
 
         public boolean matches(ComponentName component) {
-            return zen ? true : component != null && matches(component.getPackageName());
+            return all ? true : component != null && matches(component.getPackageName());
         }
 
         public boolean matches(String pkg) {
-            return zen ? true : pkg != null && pkg.toLowerCase().contains(pkgFilter);
+            return all ? true : pkg != null && pkg.toLowerCase().contains(pkgFilter);
         }
 
         @Override
         public String toString() {
-            return zen ? "zen" : ('\'' + pkgFilter + '\'');
+            return stats ? "stats" : zen ? "zen" : ('\'' + pkgFilter + '\'');
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationUsageStats.java b/services/core/java/com/android/server/notification/NotificationUsageStats.java
index 2f0cc0f..0d6e8d6 100644
--- a/services/core/java/com/android/server/notification/NotificationUsageStats.java
+++ b/services/core/java/com/android/server/notification/NotificationUsageStats.java
@@ -22,7 +22,6 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Message;
@@ -32,8 +31,14 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
 
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
 import java.io.PrintWriter;
 import java.util.ArrayDeque;
+import java.util.Calendar;
+import java.util.GregorianCalendar;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -220,6 +225,31 @@
         return result;
     }
 
+    public synchronized JSONObject dumpJson(DumpFilter filter) {
+        JSONObject dump = new JSONObject();
+        if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
+            try {
+                JSONArray aggregatedStats = new JSONArray();
+                for (AggregatedStats as : mStats.values()) {
+                    if (filter != null && !filter.matches(as.key))
+                        continue;
+                    aggregatedStats.put(as.dumpJson());
+                }
+                dump.put("current", aggregatedStats);
+            } catch (JSONException e) {
+                // pass
+            }
+        }
+        if (ENABLE_SQLITE_LOG) {
+            try {
+                dump.put("historical", mSQLiteLog.dumpJson(filter));
+            } catch (JSONException e) {
+                // pass
+            }
+        }
+        return dump;
+    }
+
     public synchronized void dump(PrintWriter pw, String indent, DumpFilter filter) {
         if (ENABLE_AGGREGATED_IN_MEMORY_STATS) {
             for (AggregatedStats as : mStats.values()) {
@@ -250,6 +280,7 @@
 
         private final Context mContext;
         public final String key;
+        private final long mCreated;
         private AggregatedStats mPrevious;
 
         // ---- Updated as the respective events occur.
@@ -285,6 +316,7 @@
         public AggregatedStats(Context context, String key) {
             this.key = key;
             mContext = context;
+            mCreated = SystemClock.elapsedRealtime();
         }
 
         public void countApiUse(NotificationRecord record) {
@@ -450,6 +482,47 @@
                     indent + "  numBlocked=" + numBlocked + ",\n" +
                     indent + "}";
         }
+
+        public JSONObject dumpJson() throws JSONException {
+            JSONObject dump = new JSONObject();
+            dump.put("key", key);
+            dump.put("duration", SystemClock.elapsedRealtime() - mCreated);
+            maybePut(dump, "numPostedByApp", numPostedByApp);
+            maybePut(dump, "numUpdatedByApp", numUpdatedByApp);
+            maybePut(dump, "numRemovedByApp", numRemovedByApp);
+            maybePut(dump, "numPeopleCacheHit", numPeopleCacheHit);
+            maybePut(dump, "numPeopleCacheMiss", numPeopleCacheMiss);
+            maybePut(dump, "numWithStaredPeople", numWithStaredPeople);
+            maybePut(dump, "numWithValidPeople", numWithValidPeople);
+            maybePut(dump, "numBlocked", numBlocked);
+            maybePut(dump, "numWithActions", numWithActions);
+            maybePut(dump, "numPrivate", numPrivate);
+            maybePut(dump, "numSecret", numSecret);
+            maybePut(dump, "numPriorityMax", numPriorityMax);
+            maybePut(dump, "numPriorityHigh", numPriorityHigh);
+            maybePut(dump, "numPriorityLow", numPriorityLow);
+            maybePut(dump, "numPriorityMin", numPriorityMin);
+            maybePut(dump, "numInterrupt", numInterrupt);
+            maybePut(dump, "numWithBigText", numWithBigText);
+            maybePut(dump, "numWithBigPicture", numWithBigPicture);
+            maybePut(dump, "numForegroundService", numForegroundService);
+            maybePut(dump, "numOngoing", numOngoing);
+            maybePut(dump, "numAutoCancel", numAutoCancel);
+            maybePut(dump, "numWithLargeIcon", numWithLargeIcon);
+            maybePut(dump, "numWithInbox", numWithInbox);
+            maybePut(dump, "numWithMediaSession", numWithMediaSession);
+            maybePut(dump, "numWithTitle", numWithTitle);
+            maybePut(dump, "numWithText", numWithText);
+            maybePut(dump, "numWithSubText", numWithSubText);
+            maybePut(dump, "numWithInfoText", numWithInfoText);
+            return dump;
+        }
+
+        private void maybePut(JSONObject dump, String name, int value) throws JSONException {
+            if (value > 0) {
+                dump.put(name, value);
+            }
+        }
     }
 
     /**
@@ -780,14 +853,51 @@
             mWriteHandler.sendMessage(mWriteHandler.obtainMessage(MSG_DISMISS, notification));
         }
 
-        public void printPostFrequencies(PrintWriter pw, String indent, DumpFilter filter) {
+        private JSONArray JsonPostFrequencies(DumpFilter filter) throws JSONException {
+            JSONArray frequencies = new JSONArray();
             SQLiteDatabase db = mHelper.getReadableDatabase();
-            long nowMs = System.currentTimeMillis();
+            long midnight = getMidnightMs();
             String q = "SELECT " +
                     COL_EVENT_USER_ID + ", " +
                     COL_PKG + ", " +
-                    // Bucket by day by looking at 'floor((nowMs - eventTimeMs) / dayMs)'
-                    "CAST(((" + nowMs + " - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
+                    // Bucket by day by looking at 'floor((midnight - eventTimeMs) / dayMs)'
+                    "CAST(((" + midnight + " - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
+                    "AS day, " +
+                    "COUNT(*) AS cnt " +
+                    "FROM " + TAB_LOG + " " +
+                    "WHERE " +
+                    COL_EVENT_TYPE + "=" + EVENT_TYPE_POST +
+                    " AND " + COL_EVENT_TIME + " > " + filter.since +
+                    " GROUP BY " + COL_EVENT_USER_ID + ", day, " + COL_PKG;
+            Cursor cursor = db.rawQuery(q, null);
+            try {
+                for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
+                    int userId = cursor.getInt(0);
+                    String pkg = cursor.getString(1);
+                    if (filter != null && !filter.matches(pkg)) continue;
+                    int day = cursor.getInt(2);
+                    int count = cursor.getInt(3);
+                    JSONObject row = new JSONObject();
+                    row.put("user_id", userId);
+                    row.put("package", pkg);
+                    row.put("day", day);
+                    row.put("count", count);
+                    frequencies.put(row);
+                }
+            } finally {
+                cursor.close();
+            }
+            return frequencies;
+        }
+
+        public void printPostFrequencies(PrintWriter pw, String indent, DumpFilter filter) {
+            SQLiteDatabase db = mHelper.getReadableDatabase();
+            long midnight = getMidnightMs();
+            String q = "SELECT " +
+                    COL_EVENT_USER_ID + ", " +
+                    COL_PKG + ", " +
+                    // Bucket by day by looking at 'floor((midnight - eventTimeMs) / dayMs)'
+                    "CAST(((" + midnight + " - " + COL_EVENT_TIME + ") / " + DAY_MS + ") AS int) " +
                         "AS day, " +
                     "COUNT(*) AS cnt " +
                     "FROM " + TAB_LOG + " " +
@@ -810,6 +920,13 @@
             }
         }
 
+        private long getMidnightMs() {
+            GregorianCalendar midnight = new GregorianCalendar();
+            midnight.set(midnight.get(Calendar.YEAR), midnight.get(Calendar.MONTH),
+                    midnight.get(Calendar.DATE), 23, 59, 59);
+            return midnight.getTimeInMillis();
+        }
+
         private void writeEvent(long eventTimeMs, int eventType, NotificationRecord r) {
             ContentValues cv = new ContentValues();
             cv.put(COL_EVENT_USER_ID, r.sbn.getUser().getIdentifier());
@@ -874,5 +991,15 @@
         public void dump(PrintWriter pw, String indent, DumpFilter filter) {
             printPostFrequencies(pw, indent, filter);
         }
+
+        public JSONObject dumpJson(DumpFilter filter) {
+            JSONObject dump = new JSONObject();
+            try {
+                dump.put("post_frequency", JsonPostFrequencies(filter));
+            } catch (JSONException e) {
+                // pass
+            }
+            return dump;
+        }
     }
 }