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;
+ }
}
}