Merge "StrictMode class instance limit interface." into honeycomb
diff --git a/api/current.xml b/api/current.xml
index 69476c3..4f566f6 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -146691,6 +146691,16 @@
  visibility="public"
 >
 </constructor>
+<constructor name="StrictMode.VmPolicy.Builder"
+ type="android.os.StrictMode.VmPolicy.Builder"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="base" type="android.os.StrictMode.VmPolicy">
+</parameter>
+</constructor>
 <method name="build"
  return="android.os.StrictMode.VmPolicy"
  abstract="false"
@@ -146768,6 +146778,21 @@
  visibility="public"
 >
 </method>
+<method name="setClassInstanceLimit"
+ return="android.os.StrictMode.VmPolicy.Builder"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="klass" type="java.lang.Class">
+</parameter>
+<parameter name="instanceLimit" type="int">
+</parameter>
+</method>
 </class>
 <class name="SystemClock"
  extends="java.lang.Object"
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ec2f771..93ad17e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -45,6 +45,7 @@
 import android.os.Looper;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.StrictMode;
 import android.text.Selection;
 import android.text.SpannableStringBuilder;
 import android.text.TextUtils;
@@ -858,6 +859,7 @@
             mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
                     ? mLastNonConfigurationInstances.fragments : null);
         }
+        StrictMode.noteActivityClass(this.getClass());
         mFragments.dispatchCreate();
         mCalled = true;
     }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 0f062cc..997ea53 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -34,6 +34,7 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -166,13 +167,19 @@
      * Note, a "VM_" bit, not thread.
      * @hide
      */
-    public static final int DETECT_VM_CURSOR_LEAKS = 0x200;  // for ProcessPolicy
+    public static final int DETECT_VM_CURSOR_LEAKS = 0x200;  // for VmPolicy
 
     /**
      * Note, a "VM_" bit, not thread.
      * @hide
      */
-    public static final int DETECT_VM_CLOSABLE_LEAKS = 0x400;  // for ProcessPolicy
+    public static final int DETECT_VM_CLOSABLE_LEAKS = 0x400;  // for VmPolicy
+
+    /**
+     * Note, a "VM_" bit, not thread.
+     * @hide
+     */
+    public static final int DETECT_VM_ACTIVITY_LEAKS = 0x800;  // for VmPolicy
 
     /**
      * @hide
@@ -232,10 +239,18 @@
             PENALTY_LOG | PENALTY_DIALOG | PENALTY_DEATH | PENALTY_DROPBOX | PENALTY_GATHER |
             PENALTY_DEATH_ON_NETWORK | PENALTY_FLASH;
 
+
+    // TODO: wrap in some ImmutableHashMap thing.
+    // Note: must be before static initialization of sVmPolicy.
+    private static final HashMap<Class, Integer> EMPTY_CLASS_LIMIT_MAP = new HashMap<Class, Integer>();
+
     /**
      * The current VmPolicy in effect.
+     *
+     * TODO: these are redundant (mask is in VmPolicy).  Should remove sVmPolicyMask.
      */
     private static volatile int sVmPolicyMask = 0;
+    private static volatile VmPolicy sVmPolicy = VmPolicy.LAX;
 
     /**
      * The number of threads trying to do an async dropbox write.
@@ -481,12 +496,19 @@
         /**
          * The default, lax policy which doesn't catch anything.
          */
-        public static final VmPolicy LAX = new VmPolicy(0);
+        public static final VmPolicy LAX = new VmPolicy(0, EMPTY_CLASS_LIMIT_MAP);
 
         final int mask;
 
-        private VmPolicy(int mask) {
+        // Map from class to max number of allowed instances in memory.
+        final HashMap<Class, Integer> classInstanceLimit;
+
+        private VmPolicy(int mask, HashMap<Class, Integer> classInstanceLimit) {
+            if (classInstanceLimit == null) {
+                throw new NullPointerException("classInstanceLimit == null");
+            }
             this.mask = mask;
+            this.classInstanceLimit = classInstanceLimit;
         }
 
         @Override
@@ -516,6 +538,49 @@
         public static final class Builder {
             private int mMask;
 
+            private HashMap<Class, Integer> mClassInstanceLimit;  // null until needed
+            private boolean mClassInstanceLimitNeedCow = false;  // need copy-on-write
+
+            public Builder() {
+                mMask = 0;
+            }
+
+            /**
+             * Build upon an existing VmPolicy.
+             */
+            public Builder(VmPolicy base) {
+                mMask = base.mask;
+                mClassInstanceLimitNeedCow = true;
+                mClassInstanceLimit = base.classInstanceLimit;
+            }
+
+            /**
+             * Set an upper bound on how many instances of a class can be in memory
+             * at once.  Helps to prevent object leaks.
+             */
+            public Builder setClassInstanceLimit(Class klass, int instanceLimit) {
+                if (klass == null) {
+                    throw new NullPointerException("klass == null");
+                }
+                if (mClassInstanceLimitNeedCow) {
+                    if (mClassInstanceLimit.containsKey(klass) &&
+                        mClassInstanceLimit.get(klass) == instanceLimit) {
+                        // no-op; don't break COW
+                        return this;
+                    }
+                    mClassInstanceLimitNeedCow = false;
+                    mClassInstanceLimit = (HashMap<Class, Integer>) mClassInstanceLimit.clone();
+                } else if (mClassInstanceLimit == null) {
+                    mClassInstanceLimit = new HashMap<Class, Integer>();
+                }
+                mClassInstanceLimit.put(klass, instanceLimit);
+                return this;
+            }
+
+            private Builder detectActivityLeaks() {
+                return enable(DETECT_VM_ACTIVITY_LEAKS);
+            }
+
             /**
              * Detect everything that's potentially suspect.
              *
@@ -524,7 +589,8 @@
              * likely expand in future releases.
              */
             public Builder detectAll() {
-                return enable(DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS);
+                return enable(DETECT_VM_ACTIVITY_LEAKS |
+                        DETECT_VM_CURSOR_LEAKS | DETECT_VM_CLOSABLE_LEAKS);
             }
 
             /**
@@ -598,7 +664,8 @@
                               PENALTY_DROPBOX | PENALTY_DIALOG)) == 0) {
                     penaltyLog();
                 }
-                return new VmPolicy(mMask);
+                return new VmPolicy(mMask,
+                        mClassInstanceLimit != null ? mClassInstanceLimit : EMPTY_CLASS_LIMIT_MAP);
             }
         }
     }
@@ -829,9 +896,7 @@
         if (IS_USER_BUILD) {
             setCloseGuardEnabled(false);
         } else {
-            sVmPolicyMask = StrictMode.DETECT_VM_CURSOR_LEAKS |
-                    StrictMode.DETECT_VM_CLOSABLE_LEAKS |
-                    StrictMode.PENALTY_DROPBOX;
+            setVmPolicy(new VmPolicy.Builder().detectAll().penaltyDropBox().build());
             setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
         }
         return true;
@@ -1289,6 +1354,7 @@
      * @param policy the policy to put into place
      */
     public static void setVmPolicy(final VmPolicy policy) {
+        sVmPolicy = policy;
         sVmPolicyMask = policy.mask;
         setCloseGuardEnabled(vmClosableObjectLeaksEnabled());
     }
@@ -1297,7 +1363,7 @@
      * Gets the current VM policy.
      */
     public static VmPolicy getVmPolicy() {
-        return new VmPolicy(sVmPolicyMask);
+        return sVmPolicy;
     }
 
     /**
@@ -1652,6 +1718,20 @@
     }
 
     /**
+     * @hide
+     */
+    public static void noteActivityClass(Class klass) {
+        if ((sVmPolicy.mask & DETECT_VM_ACTIVITY_LEAKS) == 0) {
+            return;
+        }
+        if (sVmPolicy.classInstanceLimit.containsKey(klass)) {
+            return;
+        }
+        // Note: capping at 2, not 1, to give some breathing room.
+        setVmPolicy(new VmPolicy.Builder(sVmPolicy).setClassInstanceLimit(klass, 2).build());
+    }
+
+    /**
      * Parcelable that gets sent in Binder call headers back to callers
      * to report violations that happened during a cross-process call.
      *