Merge "Work on death recipient leaks in Activity Manager and Content Service."
diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java
index a2af558..0e83dc0 100644
--- a/core/java/android/content/ContentService.java
+++ b/core/java/android/content/ContentService.java
@@ -20,17 +20,21 @@
 import android.database.IContentObserver;
 import android.database.sqlite.SQLiteException;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.util.Log;
+import android.util.SparseIntArray;
 import android.Manifest;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -70,6 +74,40 @@
             } else {
                 mSyncManager.dump(fd, pw);
             }
+            pw.println();
+            pw.println("Observer tree:");
+            synchronized (mRootNode) {
+                int[] counts = new int[2];
+                final SparseIntArray pidCounts = new SparseIntArray();
+                mRootNode.dumpLocked(fd, pw, args, "", "  ", counts, pidCounts);
+                pw.println();
+                ArrayList<Integer> sorted = new ArrayList<Integer>();
+                for (int i=0; i<pidCounts.size(); i++) {
+                    sorted.add(pidCounts.keyAt(i));
+                }
+                Collections.sort(sorted, new Comparator<Integer>() {
+                    @Override
+                    public int compare(Integer lhs, Integer rhs) {
+                        int lc = pidCounts.get(lhs);
+                        int rc = pidCounts.get(rhs);
+                        if (lc < rc) {
+                            return 1;
+                        } else if (lc > rc) {
+                            return -1;
+                        }
+                        return 0;
+                    }
+
+                });
+                for (int i=0; i<sorted.size(); i++) {
+                    int pid = sorted.get(i);
+                    pw.print("  pid "); pw.print(pid); pw.print(": ");
+                            pw.print(pidCounts.get(pid)); pw.println(" observers");
+                }
+                pw.println();
+                pw.print(" Total number of nodes: "); pw.println(counts[0]);
+                pw.print(" Total number of observers: "); pw.println(counts[1]);
+            }
         } finally {
             restoreCallingIdentity(identityToken);
         }
@@ -102,7 +140,8 @@
             throw new IllegalArgumentException("You must pass a valid uri and observer");
         }
         synchronized (mRootNode) {
-            mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode);
+            mRootNode.addObserverLocked(uri, observer, notifyForDescendents, mRootNode,
+                    Binder.getCallingUid(), Binder.getCallingPid());
             if (false) Log.v(TAG, "Registered observer " + observer + " at " + uri +
                     " with notifyForDescendents " + notifyForDescendents);
         }
@@ -465,12 +504,17 @@
     public static final class ObserverNode {
         private class ObserverEntry implements IBinder.DeathRecipient {
             public final IContentObserver observer;
+            public final int uid;
+            public final int pid;
             public final boolean notifyForDescendents;
             private final Object observersLock;
 
-            public ObserverEntry(IContentObserver o, boolean n, Object observersLock) {
+            public ObserverEntry(IContentObserver o, boolean n, Object observersLock,
+                    int _uid, int _pid) {
                 this.observersLock = observersLock;
                 observer = o;
+                uid = _uid;
+                pid = _pid;
                 notifyForDescendents = n;
                 try {
                     observer.asBinder().linkToDeath(this, 0);
@@ -484,6 +528,16 @@
                     removeObserverLocked(observer);
                 }
             }
+
+            public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+                    String name, String prefix, SparseIntArray pidCounts) {
+                pidCounts.put(pid, pidCounts.get(pid)+1);
+                pw.print(prefix); pw.print(name); pw.print(": pid=");
+                        pw.print(pid); pw.print(" uid=");
+                        pw.print(uid); pw.print(" target=");
+                        pw.println(Integer.toHexString(System.identityHashCode(
+                                observer != null ? observer.asBinder() : null)));
+            }
         }
 
         public static final int INSERT_TYPE = 0;
@@ -498,6 +552,37 @@
             mName = name;
         }
 
+        public void dumpLocked(FileDescriptor fd, PrintWriter pw, String[] args,
+                String name, String prefix, int[] counts, SparseIntArray pidCounts) {
+            String innerName = null;
+            if (mObservers.size() > 0) {
+                if ("".equals(name)) {
+                    innerName = mName;
+                } else {
+                    innerName = name + "/" + mName;
+                }
+                for (int i=0; i<mObservers.size(); i++) {
+                    counts[1]++;
+                    mObservers.get(i).dumpLocked(fd, pw, args, innerName, prefix,
+                            pidCounts);
+                }
+            }
+            if (mChildren.size() > 0) {
+                if (innerName == null) {
+                    if ("".equals(name)) {
+                        innerName = mName;
+                    } else {
+                        innerName = name + "/" + mName;
+                    }
+                }
+                for (int i=0; i<mChildren.size(); i++) {
+                    counts[0]++;
+                    mChildren.get(i).dumpLocked(fd, pw, args, innerName, prefix,
+                            counts, pidCounts);
+                }
+            }
+        }
+
         private String getUriSegment(Uri uri, int index) {
             if (uri != null) {
                 if (index == 0) {
@@ -518,15 +603,16 @@
         }
 
         public void addObserverLocked(Uri uri, IContentObserver observer,
-                boolean notifyForDescendents, Object observersLock) {
-            addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock);
+                boolean notifyForDescendents, Object observersLock, int uid, int pid) {
+            addObserverLocked(uri, 0, observer, notifyForDescendents, observersLock, uid, pid);
         }
 
         private void addObserverLocked(Uri uri, int index, IContentObserver observer,
-                boolean notifyForDescendents, Object observersLock) {
+                boolean notifyForDescendents, Object observersLock, int uid, int pid) {
             // If this is the leaf node add the observer
             if (index == countUriSegments(uri)) {
-                mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock));
+                mObservers.add(new ObserverEntry(observer, notifyForDescendents, observersLock,
+                        uid, pid));
                 return;
             }
 
@@ -539,7 +625,8 @@
             for (int i = 0; i < N; i++) {
                 ObserverNode node = mChildren.get(i);
                 if (node.mName.equals(segment)) {
-                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, observersLock);
+                    node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
+                            observersLock, uid, pid);
                     return;
                 }
             }
@@ -547,7 +634,8 @@
             // No child found, create one
             ObserverNode node = new ObserverNode(segment);
             mChildren.add(node);
-            node.addObserverLocked(uri, index + 1, observer, notifyForDescendents, observersLock);
+            node.addObserverLocked(uri, index + 1, observer, notifyForDescendents,
+                    observersLock, uid, pid);
         }
 
         public boolean removeObserverLocked(IContentObserver observer) {
diff --git a/core/tests/coretests/src/android/content/ObserverNodeTest.java b/core/tests/coretests/src/android/content/ObserverNodeTest.java
index 736c759..95b8465 100644
--- a/core/tests/coretests/src/android/content/ObserverNodeTest.java
+++ b/core/tests/coretests/src/android/content/ObserverNodeTest.java
@@ -48,9 +48,9 @@
         int[] nums = new int[] {4, 7, 1, 4, 2, 2, 3, 3};
 
         // special case
-        root.addObserverLocked(uris[0], new TestObserver().getContentObserver(), false, root);
+        root.addObserverLocked(uris[0], new TestObserver().getContentObserver(), false, root, 0, 0);
         for(int i = 1; i < uris.length; i++) {
-            root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), true, root);
+            root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), true, root, 0, 0);
         }
 
         ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
@@ -77,7 +77,7 @@
         int[] nums = new int[] {7, 1, 3, 3, 1, 1, 1, 1};
 
         for(int i = 0; i < uris.length; i++) {
-            root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), false, root);
+            root.addObserverLocked(uris[i], new TestObserver().getContentObserver(), false, root, 0, 0);
         }
 
         ArrayList<ObserverCall> calls = new ArrayList<ObserverCall>();
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index a8fb1ed..94af46d 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -3610,8 +3610,10 @@
 
         String processName = app.processName;
         try {
-            thread.asBinder().linkToDeath(new AppDeathRecipient(
-                    app, pid, thread), 0);
+            AppDeathRecipient adr = new AppDeathRecipient(
+                    app, pid, thread);
+            thread.asBinder().linkToDeath(adr, 0);
+            app.deathRecipient = adr;
         } catch (RemoteException e) {
             app.resetPackageList();
             startProcessLocked(app, "link fail", processName);
@@ -3687,6 +3689,7 @@
             Slog.w(TAG, "Exception thrown during bind!", e);
 
             app.resetPackageList();
+            app.unlinkDeathRecipient();
             startProcessLocked(app, "bind fail", processName);
             return false;
         }
@@ -9210,6 +9213,7 @@
         app.notResponding = false;
         
         app.resetPackageList();
+        app.unlinkDeathRecipient();
         app.thread = null;
         app.forcingToForeground = null;
         app.foregroundServices = false;
@@ -9327,7 +9331,6 @@
             // This app is persistent, so we need to keep its record around.
             // If it is not already on the pending app list, add it there
             // and start a new process for it.
-            app.thread = null;
             app.forcingToForeground = null;
             app.foregroundServices = false;
             if (mPersistentStartingProcesses.indexOf(app) < 0) {
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 99830f9..9e597aa 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -73,6 +73,7 @@
     int adjSeq;                 // Sequence id for identifying oom_adj assignment cycles
     int lruSeq;                 // Sequence id for identifying LRU update cycles
     CompatibilityInfo compat;   // last used compatibility mode
+    IBinder.DeathRecipient deathRecipient; // Who is watching for the death.
     ComponentName instrumentationClass;// class installed to instrument app
     ApplicationInfo instrumentationInfo; // the application being instrumented
     String instrumentationProfileFile; // where to save profiling
@@ -297,6 +298,13 @@
         }
     }
     
+    public void unlinkDeathRecipient() {
+        if (deathRecipient != null && thread != null) {
+            thread.asBinder().unlinkToDeath(deathRecipient, 0);
+        }
+        deathRecipient = null;
+    }
+
     public String toShortString() {
         if (shortStringName != null) {
             return shortStringName;