StrictMode: implement the log-to-DropBox option

Change-Id: I51d12e264155078f953028241f8c5cbdc47262e8
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 57a23ae..9834c4c 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -254,6 +254,10 @@
 
     /**
      * Describes an application crash.
+     *
+     * <p>This is also used to marshal around stack traces of ANRs and
+     * StrictMode violations which aren't necessarily crashes, but have
+     * a lot in common.
      */
     public static class CrashInfo {
         /**
@@ -292,6 +296,12 @@
         public String stackTrace;
 
         /**
+         * For StrictMode violations, the wall time duration of the
+         * violation, when known.
+         */
+        public long durationMillis = -1;
+
+        /**
          * Create an uninitialized instance of CrashInfo.
          */
         public CrashInfo() {
@@ -334,6 +344,7 @@
             throwMethodName = in.readString();
             throwLineNumber = in.readInt();
             stackTrace = in.readString();
+            durationMillis = in.readLong();
         }
 
         /**
@@ -347,6 +358,7 @@
             dest.writeString(throwMethodName);
             dest.writeInt(throwLineNumber);
             dest.writeString(stackTrace);
+            dest.writeLong(durationMillis);
         }
 
         /**
@@ -360,6 +372,9 @@
             pw.println(prefix + "throwMethodName: " + throwMethodName);
             pw.println(prefix + "throwLineNumber: " + throwLineNumber);
             pw.println(prefix + "stackTrace: " + stackTrace);
+            if (durationMillis != -1) {
+                pw.println(prefix + "durationMillis: " + durationMillis);
+            }
         }
     }
 
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index c0ae263..9b18719 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -187,6 +187,7 @@
             // Not _really_ a Crash, but we use the same data structure...
             ApplicationErrorReport.CrashInfo crashInfo =
                     new ApplicationErrorReport.CrashInfo(violation);
+            crashInfo.durationMillis = durationMillis;
 
             // Not perfect, but fast and good enough for dup suppression.
             Integer crashFingerprint = crashInfo.stackTrace.hashCode();
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index e93f7ff..ec209ed 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -648,6 +648,15 @@
             = new HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>>();
 
     /**
+     * Fingerprints (String.hashCode()) of stack traces that we've
+     * already logged DropBox entries for.  Guarded by itself.  If
+     * something (rogue user app) forces this over
+     * MAX_DUP_SUPPRESSED_STACKS entries, the contents are cleared.
+     */
+    private final HashSet<Integer> mAlreadyLoggedViolatedStacks = new HashSet<Integer>();
+    private static final int MAX_DUP_SUPPRESSED_STACKS = 5000;
+
+    /**
      * Intent broadcast that we have tried to start, but are
      * waiting for its application's process to be created.  We only
      * need one (instead of a list) because we always process broadcasts
@@ -9357,12 +9366,29 @@
     public void handleApplicationStrictModeViolation(
         IBinder app, int violationMask, ApplicationErrorReport.CrashInfo crashInfo) {
         ProcessRecord r = findAppProcess(app);
-        // TODO: implement
-        Log.w(TAG, "handleApplicationStrictModeViolation.");
 
         if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
-            Integer crashFingerprint = crashInfo.stackTrace.hashCode();
-            Log.d(TAG, "supposed to drop box for fingerprint " + crashFingerprint);
+            Integer stackFingerprint = crashInfo.stackTrace.hashCode();
+            boolean logIt = true;
+            synchronized (mAlreadyLoggedViolatedStacks) {
+                if (mAlreadyLoggedViolatedStacks.contains(stackFingerprint)) {
+                    logIt = false;
+                    // TODO: sub-sample into EventLog for these, with
+                    // the crashInfo.durationMillis?  Then we'd get
+                    // the relative pain numbers, without logging all
+                    // the stack traces repeatedly.  We'd want to do
+                    // likewise in the client code, which also does
+                    // dup suppression, before the Binder call.
+                } else {
+                    if (mAlreadyLoggedViolatedStacks.size() >= MAX_DUP_SUPPRESSED_STACKS) {
+                        mAlreadyLoggedViolatedStacks.clear();
+                    }
+                    mAlreadyLoggedViolatedStacks.add(stackFingerprint);
+                }
+            }
+            if (logIt) {
+                addErrorToDropBox("strictmode", r, null, null, null, null, null, crashInfo);
+            }
         }
 
         if ((violationMask & StrictMode.PENALTY_DIALOG) != 0) {
@@ -9375,6 +9401,8 @@
                 HashMap<String, Object> data = new HashMap<String, Object>();
                 data.put("result", result);
                 data.put("app", r);
+                data.put("violationMask", violationMask);
+                data.put("crashInfo", crashInfo);
                 msg.obj = data;
                 mHandler.sendMessage(msg);
 
@@ -9510,6 +9538,9 @@
             sb.append("Subject: ").append(subject).append("\n");
         }
         sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
+        if (crashInfo.durationMillis != -1) {
+            sb.append("Duration-Millis: ").append(crashInfo.durationMillis).append("\n");
+        }
         sb.append("\n");
 
         // Do the rest in a worker thread to avoid blocking the caller on I/O