Merge "StrictMode: time violations in Binder calls" into gingerbread
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 1fe85e6..9a55a6f 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -37,6 +37,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ServiceManager;
+import android.os.StrictMode;
 import android.text.TextUtils;
 import android.util.Config;
 import android.util.Log;
@@ -1056,8 +1057,8 @@
             data.enforceInterface(IActivityManager.descriptor);
             IBinder app = data.readStrongBinder();
             int violationMask = data.readInt();
-            ApplicationErrorReport.CrashInfo ci = new ApplicationErrorReport.CrashInfo(data);
-            handleApplicationStrictModeViolation(app, violationMask, ci);
+            StrictMode.ViolationInfo info = new StrictMode.ViolationInfo(data);
+            handleApplicationStrictModeViolation(app, violationMask, info);
             reply.writeNoException();
             return true;
         }
@@ -2571,14 +2572,14 @@
 
     public void handleApplicationStrictModeViolation(IBinder app,
             int violationMask,
-            ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException
+            StrictMode.ViolationInfo info) throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(app);
         data.writeInt(violationMask);
-        crashInfo.writeToParcel(data, 0);
+        info.writeToParcel(data, 0);
         mRemote.transact(HANDLE_APPLICATION_STRICT_MODE_VIOLATION_TRANSACTION, data, reply, 0);
         reply.readException();
         reply.recycle();
diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java
index 30815c3..8f940d5 100644
--- a/core/java/android/app/ApplicationErrorReport.java
+++ b/core/java/android/app/ApplicationErrorReport.java
@@ -24,6 +24,7 @@
 import android.content.pm.ResolveInfo;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.Printer;
@@ -74,18 +75,15 @@
     public static final int TYPE_BATTERY = 3;
 
     /**
-     * An error report about a StrictMode violation.
-     */
-    public static final int TYPE_STRICT_MODE_VIOLATION = 4;
-
-    /**
-     * An error report about a StrictMode violation.
+     * A report from a user to a developer about a running service that the
+     * user doesn't think should be running.
      */
     public static final int TYPE_RUNNING_SERVICE = 5;
 
     /**
      * Type of this report. Can be one of {@link #TYPE_NONE},
-     * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, or {@link #TYPE_BATTERY}.
+     * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, {@link #TYPE_BATTERY},
+     * or {@link #TYPE_RUNNING_SERVICE}.
      */
     public int type;
 
@@ -133,7 +131,7 @@
      * of BatteryInfo; otherwise null.
      */
     public BatteryInfo batteryInfo;
-    
+
     /**
      * If this report is of type {@link #TYPE_RUNNING_SERVICE}, contains an instance
      * of RunningServiceInfo; otherwise null.
@@ -278,10 +276,6 @@
 
     /**
      * 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 {
         /**
@@ -320,12 +314,6 @@
         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() {
@@ -368,7 +356,6 @@
             throwMethodName = in.readString();
             throwLineNumber = in.readInt();
             stackTrace = in.readString();
-            durationMillis = in.readLong();
         }
 
         /**
@@ -382,7 +369,6 @@
             dest.writeString(throwMethodName);
             dest.writeInt(throwLineNumber);
             dest.writeString(stackTrace);
-            dest.writeLong(durationMillis);
         }
 
         /**
@@ -396,9 +382,6 @@
             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/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 20c9a80..81b28b9 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -19,10 +19,10 @@
 import android.content.ComponentName;
 import android.content.ContentProviderNative;
 import android.content.IContentProvider;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.IIntentSender;
-import android.content.IIntentReceiver;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ConfigurationInfo;
@@ -31,14 +31,15 @@
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Debug;
-import android.os.RemoteException;
 import android.os.IBinder;
 import android.os.IInterface;
 import android.os.Parcel;
-import android.os.Parcelable;
 import android.os.ParcelFileDescriptor;
-import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.StrictMode;
 
 import java.util.List;
 
@@ -260,7 +261,7 @@
     // bit violated and penalty bits to be executed by the
     // ActivityManagerService remaining set.
     public void handleApplicationStrictModeViolation(IBinder app, int violationMask,
-            ApplicationErrorReport.CrashInfo crashInfo) throws RemoteException;
+            StrictMode.ViolationInfo crashInfo) throws RemoteException;
 
     /*
      * This will deliver the specified signal to all the persistent processes. Currently only 
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index d4b0500..ac12e10 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -18,6 +18,7 @@
 import android.app.ActivityManagerNative;
 import android.app.ApplicationErrorReport;
 import android.util.Log;
+import android.util.Printer;
 
 import com.android.internal.os.RuntimeInit;
 
@@ -97,9 +98,9 @@
      * via Parcel.writeNoException() (amusingly) where the caller can
      * choose how to react.
      */
-    private static final ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>> gatheredViolations =
-            new ThreadLocal<ArrayList<ApplicationErrorReport.CrashInfo>>() {
-        @Override protected ArrayList<ApplicationErrorReport.CrashInfo> initialValue() {
+    private static final ThreadLocal<ArrayList<ViolationInfo>> gatheredViolations =
+            new ThreadLocal<ArrayList<ViolationInfo>>() {
+        @Override protected ArrayList<ViolationInfo> initialValue() {
             // Starts null to avoid unnecessary allocations when
             // checking whether there are any violations or not in
             // hasGatheredViolations() below.
@@ -240,7 +241,9 @@
             if ((mPolicyMask & DISALLOW_DISK_WRITE) == 0) {
                 return;
             }
-            startHandlingViolationException(new StrictModeDiskWriteViolation(mPolicyMask));
+            BlockGuard.BlockGuardPolicyException e = new StrictModeDiskWriteViolation(mPolicyMask);
+            e.fillInStackTrace();
+            startHandlingViolationException(e);
         }
 
         // Part of BlockGuard.Policy interface:
@@ -248,7 +251,9 @@
             if ((mPolicyMask & DISALLOW_DISK_READ) == 0) {
                 return;
             }
-            startHandlingViolationException(new StrictModeDiskReadViolation(mPolicyMask));
+            BlockGuard.BlockGuardPolicyException e = new StrictModeDiskReadViolation(mPolicyMask);
+            e.fillInStackTrace();
+            startHandlingViolationException(e);
         }
 
         // Part of BlockGuard.Policy interface:
@@ -256,7 +261,9 @@
             if ((mPolicyMask & DISALLOW_NETWORK) == 0) {
                 return;
             }
-            startHandlingViolationException(new StrictModeNetworkViolation(mPolicyMask));
+            BlockGuard.BlockGuardPolicyException e = new StrictModeNetworkViolation(mPolicyMask);
+            e.fillInStackTrace();
+            startHandlingViolationException(e);
         }
 
         public void setPolicyMask(int policyMask) {
@@ -269,31 +276,70 @@
         // thread and, if so, uses it to roughly measure how long the
         // violation took.
         void startHandlingViolationException(BlockGuard.BlockGuardPolicyException e) {
-            e.fillInStackTrace();
-            final ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(e);
-            crashInfo.durationMillis = -1;  // unknown
-            final int savedPolicy = mPolicyMask;
+            final ViolationInfo info = new ViolationInfo(e, e.getPolicy());
+            info.violationUptimeMillis = SystemClock.uptimeMillis();
+            handleViolationWithTimingAttempt(info);
+        }
 
+        private static final ThreadLocal<ArrayList<ViolationInfo>> violationsBeingTimed =
+                new ThreadLocal<ArrayList<ViolationInfo>>() {
+            @Override protected ArrayList<ViolationInfo> initialValue() {
+                return new ArrayList<ViolationInfo>();
+            }
+        };
+
+        // Attempts to fill in the provided ViolationInfo's
+        // durationMillis field if this thread has a Looper we can use
+        // to measure with.  We measure from the time of violation
+        // until the time the looper is idle again (right before
+        // the next epoll_wait)
+        void handleViolationWithTimingAttempt(final ViolationInfo info) {
             Looper looper = Looper.myLooper();
+
+            // Without a Looper, we're unable to time how long the
+            // violation takes place.  This case should be rare, as
+            // most users will care about timing violations that
+            // happen on their main UI thread.  Note that this case is
+            // also hit when a violation takes place in a Binder
+            // thread, in "gather" mode.  In this case, the duration
+            // of the violation is computed by the ultimate caller and
+            // its Looper, if any.
+            // TODO: if in gather mode, ignore Looper.myLooper() and always
+            //       go into this immediate mode?
             if (looper == null) {
-                // Without a Looper, we're unable to time how long the
-                // violation takes place.  This case should be rare,
-                // as most users will care about timing violations
-                // that happen on their main UI thread.
-                handleViolation(crashInfo, savedPolicy);
-            } else {
-                MessageQueue queue = Looper.myQueue();
-                final long violationTime = SystemClock.uptimeMillis();
-                queue.addIdleHandler(new MessageQueue.IdleHandler() {
-                        public boolean queueIdle() {
-                            long afterViolationTime = SystemClock.uptimeMillis();
-                            crashInfo.durationMillis = afterViolationTime - violationTime;
-                            handleViolation(crashInfo, savedPolicy);
-                            return false;  // remove this idle handler from the array
-                        }
-                    });
+                info.durationMillis = -1;  // unknown (redundant, already set)
+                handleViolation(info);
+                return;
             }
 
+            MessageQueue queue = Looper.myQueue();
+            final ArrayList<ViolationInfo> records = violationsBeingTimed.get();
+            if (records.size() >= 10) {
+                // Not worth measuring.  Too many offenses in one loop.
+                return;
+            }
+            records.add(info);
+            if (records.size() > 1) {
+                // There's already been a violation this loop, so we've already
+                // registered an idle handler to process the list of violations
+                // at the end of this Looper's loop.
+                return;
+            }
+
+            queue.addIdleHandler(new MessageQueue.IdleHandler() {
+                    public boolean queueIdle() {
+                        long loopFinishTime = SystemClock.uptimeMillis();
+                        for (int n = 0; n < records.size(); ++n) {
+                            ViolationInfo v = records.get(n);
+                            v.violationNumThisLoop = n + 1;
+                            v.durationMillis =
+                                    (int) (loopFinishTime - v.violationUptimeMillis);
+                            handleViolation(v);
+                        }
+                        records.clear();
+                        return false;  // remove this idle handler from the array
+                    }
+                });
         }
 
         // Note: It's possible (even quite likely) that the
@@ -301,37 +347,35 @@
         // violation fired and now (after the violating code ran) due
         // to people who push/pop temporary policy in regions of code,
         // hence the policy being passed around.
-        void handleViolation(
-            final ApplicationErrorReport.CrashInfo crashInfo,
-            int policy) {
-            if (crashInfo.stackTrace == null) {
-                Log.d(TAG, "unexpected null stacktrace");
+        void handleViolation(final ViolationInfo info) {
+            if (info == null || info.crashInfo == null || info.crashInfo.stackTrace == null) {
+                Log.wtf(TAG, "unexpected null stacktrace");
                 return;
             }
 
-            if (LOG_V) Log.d(TAG, "handleViolation; policy=" + policy);
+            if (LOG_V) Log.d(TAG, "handleViolation; policy=" + info.policy);
 
-            if ((policy & PENALTY_GATHER) != 0) {
-                ArrayList<ApplicationErrorReport.CrashInfo> violations = gatheredViolations.get();
+            if ((info.policy & PENALTY_GATHER) != 0) {
+                ArrayList<ViolationInfo> violations = gatheredViolations.get();
                 if (violations == null) {
-                    violations = new ArrayList<ApplicationErrorReport.CrashInfo>(1);
+                    violations = new ArrayList<ViolationInfo>(1);
                     gatheredViolations.set(violations);
                 } else if (violations.size() >= 5) {
                     // Too many.  In a loop or something?  Don't gather them all.
                     return;
                 }
-                for (ApplicationErrorReport.CrashInfo previous : violations) {
-                    if (crashInfo.stackTrace.equals(previous.stackTrace)) {
+                for (ViolationInfo previous : violations) {
+                    if (info.crashInfo.stackTrace.equals(previous.crashInfo.stackTrace)) {
                         // Duplicate. Don't log.
                         return;
                     }
                 }
-                violations.add(crashInfo);
+                violations.add(info);
                 return;
             }
 
             // Not perfect, but fast and good enough for dup suppression.
-            Integer crashFingerprint = crashInfo.stackTrace.hashCode();
+            Integer crashFingerprint = info.crashInfo.stackTrace.hashCode();
             long lastViolationTime = 0;
             if (mLastViolationTime.containsKey(crashFingerprint)) {
                 lastViolationTime = mLastViolationTime.get(crashFingerprint);
@@ -341,13 +385,13 @@
             long timeSinceLastViolationMillis = lastViolationTime == 0 ?
                     Long.MAX_VALUE : (now - lastViolationTime);
 
-            if ((policy & PENALTY_LOG) != 0 &&
+            if ((info.policy & PENALTY_LOG) != 0 &&
                 timeSinceLastViolationMillis > MIN_LOG_INTERVAL_MS) {
-                if (crashInfo.durationMillis != -1) {
+                if (info.durationMillis != -1) {
                     Log.d(TAG, "StrictMode policy violation; ~duration=" +
-                          crashInfo.durationMillis + " ms: " + crashInfo.stackTrace);
+                          info.durationMillis + " ms: " + info.crashInfo.stackTrace);
                 } else {
-                    Log.d(TAG, "StrictMode policy violation: " + crashInfo.stackTrace);
+                    Log.d(TAG, "StrictMode policy violation: " + info.crashInfo.stackTrace);
                 }
             }
 
@@ -355,20 +399,20 @@
             // subset of the original StrictMode policy bitmask, with
             // only the bit violated and penalty bits to be executed
             // by the ActivityManagerService remaining set.
-            int violationMask = 0;
+            int violationMaskSubset = 0;
 
-            if ((policy & PENALTY_DIALOG) != 0 &&
+            if ((info.policy & PENALTY_DIALOG) != 0 &&
                 timeSinceLastViolationMillis > MIN_DIALOG_INTERVAL_MS) {
-                violationMask |= PENALTY_DIALOG;
+                violationMaskSubset |= PENALTY_DIALOG;
             }
 
-            if ((policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
-                violationMask |= PENALTY_DROPBOX;
+            if ((info.policy & PENALTY_DROPBOX) != 0 && lastViolationTime == 0) {
+                violationMaskSubset |= PENALTY_DROPBOX;
             }
 
-            if (violationMask != 0) {
-                int violationBit = parseViolationFromMessage(crashInfo.exceptionMessage);
-                violationMask |= violationBit;
+            if (violationMaskSubset != 0) {
+                int violationBit = parseViolationFromMessage(info.crashInfo.exceptionMessage);
+                violationMaskSubset |= violationBit;
                 final int savedPolicy = getThreadBlockingPolicy();
                 try {
                     // First, remove any policy before we call into the Activity Manager,
@@ -379,8 +423,8 @@
 
                     ActivityManagerNative.getDefault().handleApplicationStrictModeViolation(
                         RuntimeInit.getApplicationObject(),
-                        violationMask,
-                        crashInfo);
+                        violationMaskSubset,
+                        info);
                 } catch (RemoteException e) {
                     Log.e(TAG, "RemoteException trying to handle StrictMode violation", e);
                 } finally {
@@ -389,7 +433,7 @@
                 }
             }
 
-            if ((policy & PENALTY_DEATH) != 0) {
+            if ((info.policy & PENALTY_DEATH) != 0) {
                 System.err.println("StrictMode policy violation with POLICY_DEATH; shutting down.");
                 Process.killProcess(Process.myPid());
                 System.exit(10);
@@ -417,7 +461,7 @@
      * Called from Parcel.writeNoException()
      */
     /* package */ static void writeGatheredViolationsToParcel(Parcel p) {
-        ArrayList<ApplicationErrorReport.CrashInfo> violations = gatheredViolations.get();
+        ArrayList<ViolationInfo> violations = gatheredViolations.get();
         if (violations == null) {
             p.writeInt(0);
         } else {
@@ -439,35 +483,21 @@
      */
     /* package */ static void readAndHandleBinderCallViolations(Parcel p) {
         // Our own stack trace to append
-        Exception e = new LogStackTrace();
         StringWriter sw = new StringWriter();
-        e.printStackTrace(new PrintWriter(sw));
+        new LogStackTrace().printStackTrace(new PrintWriter(sw));
         String ourStack = sw.toString();
 
         int policyMask = getThreadBlockingPolicy();
+        boolean currentlyGathering = (policyMask & PENALTY_GATHER) != 0;
 
         int numViolations = p.readInt();
         for (int i = 0; i < numViolations; ++i) {
             if (LOG_V) Log.d(TAG, "strict mode violation stacks read from binder call.  i=" + i);
-            ApplicationErrorReport.CrashInfo crashInfo = new ApplicationErrorReport.CrashInfo(p);
-            crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack;
-
-            // Unlike the in-process violations in which case we
-            // trigger an error _before_ the thing occurs, in this
-            // case the violating thing has already occurred, so we
-            // can't use our heuristic of waiting for the next event
-            // loop idle cycle to measure the approximate violation
-            // duration.  Instead, just skip that step and use -1
-            // (unknown duration) for now.
-            // TODO: keep a thread-local on remote process of first
-            // violation time's uptimeMillis, and when writing that
-            // back out in Parcel reply, include in the header the
-            // violation time and use it here.
-            crashInfo.durationMillis = -1;
-
+            ViolationInfo info = new ViolationInfo(p, !currentlyGathering);
+            info.crashInfo.stackTrace += "# via Binder call with stack:\n" + ourStack;
             BlockGuard.Policy policy = BlockGuard.getThreadPolicy();
             if (policy instanceof AndroidBlockGuardPolicy) {
-                ((AndroidBlockGuardPolicy) policy).handleViolation(crashInfo, policyMask);
+                ((AndroidBlockGuardPolicy) policy).handleViolationWithTimingAttempt(info);
             }
         }
     }
@@ -483,4 +513,113 @@
     private static void onBinderStrictModePolicyChange(int newPolicy) {
         setBlockGuardPolicy(newPolicy);
     }
+
+    /**
+     * Parcelable that gets sent in Binder call headers back to callers
+     * to report violations that happened during a cross-process call.
+     *
+     * @hide
+     */
+    public static class ViolationInfo {
+        /**
+         * Stack and other stuff info.
+         */
+        public final ApplicationErrorReport.CrashInfo crashInfo;
+
+        /**
+         * The strict mode policy mask at the time of violation.
+         */
+        public final int policy;
+
+        /**
+         * The wall time duration of the violation, when known.  -1 when
+         * not known.
+         */
+        public int durationMillis = -1;
+
+        /**
+         * Which violation number this was (1-based) since the last Looper loop,
+         * from the perspective of the root caller (if it crossed any processes
+         * via Binder calls).  The value is 0 if the root caller wasn't on a Looper
+         * thread.
+         */
+        public int violationNumThisLoop;
+
+        /**
+         * The time (in terms of SystemClock.uptimeMillis()) that the
+         * violation occurred.
+         */
+        public long violationUptimeMillis;
+
+        /**
+         * Create an uninitialized instance of ViolationInfo
+         */
+        public ViolationInfo() {
+            crashInfo = null;
+            policy = 0;
+        }
+
+        /**
+         * Create an instance of ViolationInfo initialized from an exception.
+         */
+        public ViolationInfo(Throwable tr, int policy) {
+            crashInfo = new ApplicationErrorReport.CrashInfo(tr);
+            violationUptimeMillis = SystemClock.uptimeMillis();
+            this.policy = policy;
+        }
+
+        /**
+         * Create an instance of ViolationInfo initialized from a Parcel.
+         */
+        public ViolationInfo(Parcel in) {
+            this(in, false);
+        }
+
+        /**
+         * Create an instance of ViolationInfo initialized from a Parcel.
+         *
+         * @param unsetGatheringBit if true, the caller is the root caller
+         *   and the gathering penalty should be removed.
+         */
+        public ViolationInfo(Parcel in, boolean unsetGatheringBit) {
+            crashInfo = new ApplicationErrorReport.CrashInfo(in);
+            int rawPolicy = in.readInt();
+            if (unsetGatheringBit) {
+                policy = rawPolicy & ~PENALTY_GATHER;
+            } else {
+                policy = rawPolicy;
+            }
+            durationMillis = in.readInt();
+            violationNumThisLoop = in.readInt();
+            violationUptimeMillis = in.readLong();
+        }
+
+        /**
+         * Save a ViolationInfo instance to a parcel.
+         */
+        public void writeToParcel(Parcel dest, int flags) {
+            crashInfo.writeToParcel(dest, flags);
+            dest.writeInt(policy);
+            dest.writeInt(durationMillis);
+            dest.writeInt(violationNumThisLoop);
+            dest.writeLong(violationUptimeMillis);
+        }
+
+
+        /**
+         * Dump a ViolationInfo instance to a Printer.
+         */
+        public void dump(Printer pw, String prefix) {
+            crashInfo.dump(pw, prefix);
+            pw.println(prefix + "policy: " + policy);
+            if (durationMillis != -1) {
+                pw.println(prefix + "durationMillis: " + durationMillis);
+            }
+            if (violationNumThisLoop != 0) {
+                pw.println(prefix + "violationNumThisLoop: " + violationNumThisLoop);
+            }
+            pw.println(prefix + "violationUptimeMillis: " + violationUptimeMillis);
+        }
+
+    }
 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index bcbda3e..162fffe 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -6101,17 +6101,19 @@
     }
 
     public void handleApplicationStrictModeViolation(
-        IBinder app, int violationMask, ApplicationErrorReport.CrashInfo crashInfo) {
+            IBinder app,
+            int violationMask,
+            StrictMode.ViolationInfo info) {
         ProcessRecord r = findAppProcess(app);
 
         if ((violationMask & StrictMode.PENALTY_DROPBOX) != 0) {
-            Integer stackFingerprint = crashInfo.stackTrace.hashCode();
+            Integer stackFingerprint = info.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 info.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
@@ -6124,7 +6126,7 @@
                 }
             }
             if (logIt) {
-                logStrictModeViolationToDropBox(r, crashInfo);
+                logStrictModeViolationToDropBox(r, info);
             }
         }
 
@@ -6139,7 +6141,7 @@
                 data.put("result", result);
                 data.put("app", r);
                 data.put("violationMask", violationMask);
-                data.put("crashInfo", crashInfo);
+                data.put("info", info);
                 msg.obj = data;
                 mHandler.sendMessage(msg);
 
@@ -6154,9 +6156,10 @@
     // these in quick succession so we try to batch these together to
     // minimize disk writes, number of dropbox entries, and maximize
     // compression, by having more fewer, larger records.
-    private void logStrictModeViolationToDropBox(ProcessRecord process,
-                                                 ApplicationErrorReport.CrashInfo crashInfo) {
-        if (crashInfo == null) {
+    private void logStrictModeViolationToDropBox(
+            ProcessRecord process,
+            StrictMode.ViolationInfo info) {
+        if (info == null) {
             return;
         }
         final boolean isSystemApp = process == null ||
@@ -6177,12 +6180,16 @@
             appendDropBoxProcessHeaders(process, sb);
             sb.append("Build: ").append(Build.FINGERPRINT).append("\n");
             sb.append("System-App: ").append(isSystemApp).append("\n");
-            if (crashInfo != null && crashInfo.durationMillis != -1) {
-                sb.append("Duration-Millis: ").append(crashInfo.durationMillis).append("\n");
+            sb.append("Uptime-Millis: ").append(info.violationUptimeMillis).append("\n");
+            if (info.violationNumThisLoop != 0) {
+                sb.append("Loop-Violation-Number: ").append(info.violationNumThisLoop).append("\n");
+            }
+            if (info != null && info.durationMillis != -1) {
+                sb.append("Duration-Millis: ").append(info.durationMillis).append("\n");
             }
             sb.append("\n");
-            if (crashInfo != null && crashInfo.stackTrace != null) {
-                sb.append(crashInfo.stackTrace);
+            if (info.crashInfo != null && info.crashInfo.stackTrace != null) {
+                sb.append(info.crashInfo.stackTrace);
             }
             sb.append("\n");