am ae5811c7: Merge "Fix (mostly) issue #5109947: Race condition between retrieving a..." into jb-dev

* commit 'ae5811c71405878b09eace395ec2b28e54eeb427':
  Fix (mostly) issue #5109947: Race condition between retrieving a...
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 4506546..2ed93f4 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -580,7 +580,8 @@
             IBinder b = data.readStrongBinder();
             IApplicationThread app = ApplicationThreadNative.asInterface(b);
             String name = data.readString();
-            ContentProviderHolder cph = getContentProvider(app, name);
+            boolean stable = data.readInt() != 0;
+            ContentProviderHolder cph = getContentProvider(app, name, stable);
             reply.writeNoException();
             if (cph != null) {
                 reply.writeInt(1);
@@ -617,12 +618,30 @@
             return true;
         }
 
+        case REF_CONTENT_PROVIDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            int stable = data.readInt();
+            int unstable = data.readInt();
+            boolean res = refContentProvider(b, stable, unstable);
+            reply.writeNoException();
+            reply.writeInt(res ? 1 : 0);
+            return true;
+        }
+
+        case UNSTABLE_PROVIDER_DIED_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            unstableProviderDied(b);
+            reply.writeNoException();
+            return true;
+        }
+
         case REMOVE_CONTENT_PROVIDER_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder b = data.readStrongBinder();
-            IApplicationThread app = ApplicationThreadNative.asInterface(b);
-            String name = data.readString();
-            removeContentProvider(app, name);
+            boolean stable = data.readInt() != 0;
+            removeContentProvider(b, stable);
             reply.writeNoException();
             return true;
         }
@@ -2314,13 +2333,13 @@
         reply.recycle();
     }
     public ContentProviderHolder getContentProvider(IApplicationThread caller,
-                                                    String name) throws RemoteException
-    {
+            String name, boolean stable) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(caller != null ? caller.asBinder() : null);
         data.writeString(name);
+        data.writeInt(stable ? 1 : 0);
         mRemote.transact(GET_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
         reply.readException();
         int res = reply.readInt();
@@ -2352,7 +2371,7 @@
         return cph;
     }
     public void publishContentProviders(IApplicationThread caller,
-                                        List<ContentProviderHolder> providers) throws RemoteException
+            List<ContentProviderHolder> providers) throws RemoteException
     {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -2364,14 +2383,38 @@
         data.recycle();
         reply.recycle();
     }
-    
-    public void removeContentProvider(IApplicationThread caller,
-            String name) throws RemoteException {
+    public boolean refContentProvider(IBinder connection, int stable, int unstable)
+            throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
-        data.writeStrongBinder(caller != null ? caller.asBinder() : null);
-        data.writeString(name);
+        data.writeStrongBinder(connection);
+        data.writeInt(stable);
+        data.writeInt(unstable);
+        mRemote.transact(REF_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean res = reply.readInt() != 0;
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+    public void unstableProviderDied(IBinder connection) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(connection);
+        mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(connection);
+        data.writeInt(stable ? 1 : 0);
         mRemote.transact(REMOVE_CONTENT_PROVIDER_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 33e639e..a457e3c 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -140,6 +140,7 @@
     private static final boolean DEBUG_CONFIGURATION = false;
     private static final boolean DEBUG_SERVICE = false;
     private static final boolean DEBUG_MEMORY_TRIM = false;
+    private static final boolean DEBUG_PROVIDER = false;
     private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
     private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";");
     private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
@@ -210,6 +211,8 @@
         = new HashMap<IBinder, ProviderRefCount>();
     final HashMap<IBinder, ProviderClientRecord> mLocalProviders
         = new HashMap<IBinder, ProviderClientRecord>();
+    final HashMap<ComponentName, ProviderClientRecord> mLocalProvidersByName
+            = new HashMap<ComponentName, ProviderClientRecord>();
 
     final HashMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
         = new HashMap<Activity, ArrayList<OnActivityPausedListener>>();
@@ -284,20 +287,19 @@
         }
     }
 
-    final class ProviderClientRecord implements IBinder.DeathRecipient {
-        final String mName;
+    final class ProviderClientRecord {
+        final String[] mNames;
         final IContentProvider mProvider;
         final ContentProvider mLocalProvider;
+        final IActivityManager.ContentProviderHolder mHolder;
 
-        ProviderClientRecord(String name, IContentProvider provider,
-                ContentProvider localProvider) {
-            mName = name;
+        ProviderClientRecord(String[] names, IContentProvider provider,
+                ContentProvider localProvider,
+                IActivityManager.ContentProviderHolder holder) {
+            mNames = names;
             mProvider = provider;
             mLocalProvider = localProvider;
-        }
-
-        public void binderDied() {
-            removeDeadProvider(mName, mProvider);
+            mHolder = holder;
         }
     }
 
@@ -1061,6 +1063,11 @@
             pw.flush();
         }
 
+        @Override
+        public void unstableProviderDied(IBinder provider) {
+            queueOrSendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
+        }
+
         private void printRow(PrintWriter pw, String format, Object...objs) {
             pw.println(String.format(format, objs));
         }
@@ -1125,6 +1132,7 @@
         public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
         public static final int TRIM_MEMORY             = 140;
         public static final int DUMP_PROVIDER           = 141;
+        public static final int UNSTABLE_PROVIDER_DIED  = 142;
         String codeToString(int code) {
             if (DEBUG_MESSAGES) {
                 switch (code) {
@@ -1170,6 +1178,7 @@
                     case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
                     case TRIM_MEMORY: return "TRIM_MEMORY";
                     case DUMP_PROVIDER: return "DUMP_PROVIDER";
+                    case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
                 }
             }
             return Integer.toString(code);
@@ -1337,7 +1346,7 @@
                     break;
                 case REMOVE_PROVIDER:
                     Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove");
-                    completeRemoveProvider((IContentProvider)msg.obj);
+                    completeRemoveProvider((ProviderRefCount)msg.obj);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
                 case ENABLE_JIT:
@@ -1377,6 +1386,9 @@
                     handleTrimMemory(msg.arg1);
                     Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                     break;
+                case UNSTABLE_PROVIDER_DIED:
+                    handleUnstableProviderDied((IBinder)msg.obj, false);
+                    break;
             }
             if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
         }
@@ -2867,10 +2879,24 @@
     }
 
     private static final class ProviderRefCount {
-        public int count;
+        public final IActivityManager.ContentProviderHolder holder;
+        public final ProviderClientRecord client;
+        public int stableCount;
+        public int unstableCount;
 
-        ProviderRefCount(int pCount) {
-            count = pCount;
+        // When this is set, the stable and unstable ref counts are 0 and
+        // we have a pending operation scheduled to remove the ref count
+        // from the activity manager.  On the activity manager we are still
+        // holding an unstable ref, though it is not reflected in the counts
+        // here.
+        public boolean removePending;
+
+        ProviderRefCount(IActivityManager.ContentProviderHolder inHolder,
+                ProviderClientRecord inClient, int sCount, int uCount) {
+            holder = inHolder;
+            client = inClient;
+            stableCount = sCount;
+            unstableCount = uCount;
         }
     }
 
@@ -4080,15 +4106,6 @@
                 Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
             }
 
-            try {
-                mInstrumentation.onCreate(data.instrumentationArgs);
-            }
-            catch (Exception e) {
-                throw new RuntimeException(
-                    "Exception thrown in onCreate() of "
-                    + data.instrumentationName + ": " + e.toString(), e);
-            }
-
         } else {
             mInstrumentation = new Instrumentation();
         }
@@ -4119,6 +4136,17 @@
                 }
             }
 
+            // Do this after providers, since instrumentation tests generally start their
+            // test thread at this point, and we don't want that racing.
+            try {
+                mInstrumentation.onCreate(data.instrumentationArgs);
+            }
+            catch (Exception e) {
+                throw new RuntimeException(
+                    "Exception thrown in onCreate() of "
+                    + data.instrumentationName + ": " + e.toString(), e);
+            }
+
             try {
                 mInstrumentation.callApplicationOnCreate(app);
             } catch (Exception e) {
@@ -4159,12 +4187,9 @@
             buf.append(": ");
             buf.append(cpi.name);
             Log.i(TAG, buf.toString());
-            IContentProvider cp = installProvider(context, null, cpi,
-                    false /*noisy*/, true /*noReleaseNeeded*/);
-            if (cp != null) {
-                IActivityManager.ContentProviderHolder cph =
-                        new IActivityManager.ContentProviderHolder(cpi);
-                cph.provider = cp;
+            IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
+                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
+            if (cph != null) {
                 cph.noReleaseNeeded = true;
                 results.add(cph);
             }
@@ -4177,8 +4202,8 @@
         }
     }
 
-    public final IContentProvider acquireProvider(Context c, String name) {
-        IContentProvider provider = acquireExistingProvider(c, name);
+    public final IContentProvider acquireProvider(Context c, String name, boolean stable) {
+        IContentProvider provider = acquireExistingProvider(c, name, stable);
         if (provider != null) {
             return provider;
         }
@@ -4192,7 +4217,7 @@
         IActivityManager.ContentProviderHolder holder = null;
         try {
             holder = ActivityManagerNative.getDefault().getContentProvider(
-                    getApplicationThread(), name);
+                    getApplicationThread(), name, stable);
         } catch (RemoteException ex) {
         }
         if (holder == null) {
@@ -4202,23 +4227,79 @@
 
         // Install provider will increment the reference count for us, and break
         // any ties in the race.
-        provider = installProvider(c, holder.provider, holder.info,
-                true /*noisy*/, holder.noReleaseNeeded);
-        if (holder.provider != null && provider != holder.provider) {
-            if (localLOGV) {
-                Slog.v(TAG, "acquireProvider: lost the race, releasing extraneous "
-                        + "reference to the content provider");
-            }
-            try {
-                ActivityManagerNative.getDefault().removeContentProvider(
-                        getApplicationThread(), name);
-            } catch (RemoteException ex) {
-            }
-        }
-        return provider;
+        holder = installProvider(c, holder, holder.info,
+                true /*noisy*/, holder.noReleaseNeeded, stable);
+        return holder.provider;
     }
 
-    public final IContentProvider acquireExistingProvider(Context c, String name) {
+    private final void incProviderRefLocked(ProviderRefCount prc, boolean stable) {
+        if (stable) {
+            prc.stableCount += 1;
+            if (prc.stableCount == 1) {
+                // We are acquiring a new stable reference on the provider.
+                int unstableDelta;
+                if (prc.removePending) {
+                    // We have a pending remove operation, which is holding the
+                    // last unstable reference.  At this point we are converting
+                    // that unstable reference to our new stable reference.
+                    unstableDelta = -1;
+                    // Cancel the removal of the provider.
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "incProviderRef: stable "
+                                + "snatched provider from the jaws of death");
+                    }
+                    prc.removePending = false;
+                    mH.removeMessages(H.REMOVE_PROVIDER, prc);
+                } else {
+                    unstableDelta = 0;
+                }
+                try {
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "incProviderRef Now stable - "
+                                + prc.holder.info.name + ": unstableDelta="
+                                + unstableDelta);
+                    }
+                    ActivityManagerNative.getDefault().refContentProvider(
+                            prc.holder.connection, 1, unstableDelta);
+                } catch (RemoteException e) {
+                    //do nothing content provider object is dead any way
+                }
+            }
+        } else {
+            prc.unstableCount += 1;
+            if (prc.unstableCount == 1) {
+                // We are acquiring a new unstable reference on the provider.
+                if (prc.removePending) {
+                    // Oh look, we actually have a remove pending for the
+                    // provider, which is still holding the last unstable
+                    // reference.  We just need to cancel that to take new
+                    // ownership of the reference.
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "incProviderRef: unstable "
+                                + "snatched provider from the jaws of death");
+                    }
+                    prc.removePending = false;
+                    mH.removeMessages(H.REMOVE_PROVIDER, prc);
+                } else {
+                    // First unstable ref, increment our count in the
+                    // activity manager.
+                    try {
+                        if (DEBUG_PROVIDER) {
+                            Slog.v(TAG, "incProviderRef: Now unstable - "
+                                    + prc.holder.info.name);
+                        }
+                        ActivityManagerNative.getDefault().refContentProvider(
+                                prc.holder.connection, 0, 1);
+                    } catch (RemoteException e) {
+                        //do nothing content provider object is dead any way
+                    }
+                }
+            }
+        }
+    }
+
+    public final IContentProvider acquireExistingProvider(Context c, String name,
+            boolean stable) {
         synchronized (mProviderMap) {
             ProviderClientRecord pr = mProviderMap.get(name);
             if (pr == null) {
@@ -4232,23 +4313,14 @@
             // provider is not reference counted and never needs to be released.
             ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
             if (prc != null) {
-                prc.count += 1;
-                if (prc.count == 1) {
-                    if (localLOGV) {
-                        Slog.v(TAG, "acquireExistingProvider: "
-                                + "snatched provider from the jaws of death");
-                    }
-                    // Because the provider previously had a reference count of zero,
-                    // it was scheduled to be removed.  Cancel that.
-                    mH.removeMessages(H.REMOVE_PROVIDER, provider);
-                }
+                incProviderRefLocked(prc, stable);
             }
             return provider;
         }
     }
 
-    public final boolean releaseProvider(IContentProvider provider) {
-        if(provider == null) {
+    public final boolean releaseProvider(IContentProvider provider, boolean stable) {
+        if (provider == null) {
             return false;
         }
 
@@ -4260,55 +4332,98 @@
                 return false;
             }
 
-            if (prc.count == 0) {
-                if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?");
-                return false;
+            boolean lastRef = false;
+            if (stable) {
+                if (prc.stableCount == 0) {
+                    if (DEBUG_PROVIDER) Slog.v(TAG,
+                            "releaseProvider: stable ref count already 0, how?");
+                    return false;
+                }
+                prc.stableCount -= 1;
+                if (prc.stableCount == 0) {
+                    // What we do at this point depends on whether there are
+                    // any unstable refs left: if there are, we just tell the
+                    // activity manager to decrement its stable count; if there
+                    // aren't, we need to enqueue this provider to be removed,
+                    // and convert to holding a single unstable ref while
+                    // doing so.
+                    lastRef = prc.unstableCount == 0;
+                    try {
+                        if (DEBUG_PROVIDER) {
+                            Slog.v(TAG, "releaseProvider: No longer stable w/lastRef="
+                                    + lastRef + " - " + prc.holder.info.name);
+                        }
+                        ActivityManagerNative.getDefault().refContentProvider(
+                                prc.holder.connection, -1, lastRef ? 1 : 0);
+                    } catch (RemoteException e) {
+                        //do nothing content provider object is dead any way
+                    }
+                }
+            } else {
+                if (prc.unstableCount == 0) {
+                    if (DEBUG_PROVIDER) Slog.v(TAG,
+                            "releaseProvider: unstable ref count already 0, how?");
+                    return false;
+                }
+                prc.unstableCount -= 1;
+                if (prc.unstableCount == 0) {
+                    // If this is the last reference, we need to enqueue
+                    // this provider to be removed instead of telling the
+                    // activity manager to remove it at this point.
+                    lastRef = prc.stableCount == 0;
+                    if (!lastRef) {
+                        try {
+                            if (DEBUG_PROVIDER) {
+                                Slog.v(TAG, "releaseProvider: No longer unstable - "
+                                        + prc.holder.info.name);
+                            }
+                            ActivityManagerNative.getDefault().refContentProvider(
+                                    prc.holder.connection, 0, -1);
+                        } catch (RemoteException e) {
+                            //do nothing content provider object is dead any way
+                        }
+                    }
+                }
             }
 
-            prc.count -= 1;
-            if (prc.count == 0) {
-                // Schedule the actual remove asynchronously, since we don't know the context
-                // this will be called in.
-                // TODO: it would be nice to post a delayed message, so
-                // if we come back and need the same provider quickly
-                // we will still have it available.
-                Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider);
-                mH.sendMessage(msg);
+            if (lastRef) {
+                if (!prc.removePending) {
+                    // Schedule the actual remove asynchronously, since we don't know the context
+                    // this will be called in.
+                    // TODO: it would be nice to post a delayed message, so
+                    // if we come back and need the same provider quickly
+                    // we will still have it available.
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "releaseProvider: Enqueueing pending removal - "
+                                + prc.holder.info.name);
+                    }
+                    prc.removePending = true;
+                    Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, prc);
+                    mH.sendMessage(msg);
+                } else {
+                    Slog.w(TAG, "Duplicate remove pending of provider " + prc.holder.info.name);
+                }
             }
             return true;
         }
     }
 
-    public final IContentProvider acquireUnstableProvider(Context c, String name) {
-        return acquireProvider(c, name);
-    }
-
-    public final boolean releaseUnstableProvider(IContentProvider provider) {
-        return releaseProvider(provider);
-    }
-
-    final void completeRemoveProvider(IContentProvider provider) {
-        IBinder jBinder = provider.asBinder();
-        String remoteProviderName = null;
-        synchronized(mProviderMap) {
-            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
-            if (prc == null) {
-                // Either no release is needed (so we shouldn't be here) or the
-                // provider was already released.
-                if (localLOGV) Slog.v(TAG, "completeRemoveProvider: release not needed");
-                return;
-            }
-
-            if (prc.count != 0) {
+    final void completeRemoveProvider(ProviderRefCount prc) {
+        synchronized (mProviderMap) {
+            if (!prc.removePending) {
                 // There was a race!  Some other client managed to acquire
                 // the provider before the removal was completed.
                 // Abort the removal.  We will do it later.
-                if (localLOGV) Slog.v(TAG, "completeRemoveProvider: lost the race, "
+                if (DEBUG_PROVIDER) Slog.v(TAG, "completeRemoveProvider: lost the race, "
                         + "provider still in use");
                 return;
             }
 
-            mProviderRefCountMap.remove(jBinder);
+            final IBinder jBinder = prc.holder.provider.asBinder();
+            ProviderRefCount existingPrc = mProviderRefCountMap.get(jBinder);
+            if (existingPrc == prc) {
+                mProviderRefCountMap.remove(jBinder);
+            }
 
             Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
             while (iter.hasNext()) {
@@ -4316,41 +4431,70 @@
                 IBinder myBinder = pr.mProvider.asBinder();
                 if (myBinder == jBinder) {
                     iter.remove();
-                    if (pr.mLocalProvider == null) {
-                        myBinder.unlinkToDeath(pr, 0);
-                        if (remoteProviderName == null) {
-                            remoteProviderName = pr.mName;
+                }
+            }
+        }
+
+        try {
+            if (DEBUG_PROVIDER) {
+                Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative."
+                        + "removeContentProvider(" + prc.holder.info.name + ")");
+            }
+            ActivityManagerNative.getDefault().removeContentProvider(
+                    prc.holder.connection, false);
+        } catch (RemoteException e) {
+            //do nothing content provider object is dead any way
+        }
+    }
+
+    final void handleUnstableProviderDied(IBinder provider, boolean fromClient) {
+        synchronized(mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(provider);
+            if (prc != null) {
+                if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider "
+                        + provider + " " + prc.holder.info.name);
+                mProviderRefCountMap.remove(provider);
+                if (prc.client != null && prc.client.mNames != null) {
+                    for (String name : prc.client.mNames) {
+                        ProviderClientRecord pr = mProviderMap.get(name);
+                        if (pr != null && pr.mProvider.asBinder() == provider) {
+                            Slog.i(TAG, "Removing dead content provider: " + name);
+                            mProviderMap.remove(name);
                         }
                     }
                 }
+                if (fromClient) {
+                    // We found out about this due to execution in our client
+                    // code.  Tell the activity manager about it now, to ensure
+                    // that the next time we go to do anything with the provider
+                    // it knows it is dead (so we don't race with its death
+                    // notification).
+                    try {
+                        ActivityManagerNative.getDefault().unstableProviderDied(
+                                prc.holder.connection);
+                    } catch (RemoteException e) {
+                        //do nothing content provider object is dead any way
+                    }
+                }
             }
         }
-
-        if (remoteProviderName != null) {
-            try {
-                if (localLOGV) {
-                    Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative."
-                            + "removeContentProvider(" + remoteProviderName + ")");
-                }
-                ActivityManagerNative.getDefault().removeContentProvider(
-                        getApplicationThread(), remoteProviderName);
-            } catch (RemoteException e) {
-                //do nothing content provider object is dead any way
-            }
-        }
     }
 
-    final void removeDeadProvider(String name, IContentProvider provider) {
-        synchronized(mProviderMap) {
-            ProviderClientRecord pr = mProviderMap.get(name);
-            if (pr != null && pr.mProvider.asBinder() == provider.asBinder()) {
-                Slog.i(TAG, "Removing dead content provider: " + name);
-                ProviderClientRecord removed = mProviderMap.remove(name);
-                if (removed != null) {
-                    removed.mProvider.asBinder().unlinkToDeath(removed, 0);
-                }
+    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
+            ContentProvider localProvider,IActivityManager.ContentProviderHolder holder) {
+        String names[] = PATTERN_SEMICOLON.split(holder.info.authority);
+        ProviderClientRecord pcr = new ProviderClientRecord(names, provider,
+                localProvider, holder);
+        for (int i = 0; i < names.length; i++) {
+            ProviderClientRecord existing = mProviderMap.get(names[i]);
+            if (existing != null) {
+                Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
+                        + " already published as " + names[i]);
+            } else {
+                mProviderMap.put(names[i], pcr);
             }
         }
+        return pcr;
     }
 
     /**
@@ -4367,12 +4511,13 @@
      * and returns the existing provider.  This can happen due to concurrent
      * attempts to acquire the same provider.
      */
-    private IContentProvider installProvider(Context context,
-            IContentProvider provider, ProviderInfo info,
-            boolean noisy, boolean noReleaseNeeded) {
+    private IActivityManager.ContentProviderHolder installProvider(Context context,
+            IActivityManager.ContentProviderHolder holder, ProviderInfo info,
+            boolean noisy, boolean noReleaseNeeded, boolean stable) {
         ContentProvider localProvider = null;
-        if (provider == null) {
-            if (noisy) {
+        IContentProvider provider;
+        if (holder == null) {
+            if (DEBUG_PROVIDER || noisy) {
                 Slog.d(TAG, "Loading provider " + info.authority + ": "
                         + info.name);
             }
@@ -4409,7 +4554,7 @@
                           info.applicationInfo.sourceDir);
                     return null;
                 }
-                if (false) Slog.v(
+                if (DEBUG_PROVIDER) Slog.v(
                     TAG, "Instantiating local provider " + info.name);
                 // XXX Need to create the correct context for this provider.
                 localProvider.attachInfo(c, info);
@@ -4421,76 +4566,72 @@
                 }
                 return null;
             }
-        } else if (localLOGV) {
-            Slog.v(TAG, "Installing external provider " + info.authority + ": "
+        } else {
+            provider = holder.provider;
+            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                     + info.name);
         }
 
-        synchronized (mProviderMap) {
-            // There is a possibility that this thread raced with another thread to
-            // add the provider.  If we find another thread got there first then we
-            // just get out of the way and return the original provider.
-            IBinder jBinder = provider.asBinder();
-            String names[] = PATTERN_SEMICOLON.split(info.authority);
-            for (int i = 0; i < names.length; i++) {
-                ProviderClientRecord pr = mProviderMap.get(names[i]);
-                if (pr != null) {
-                    if (localLOGV) {
-                        Slog.v(TAG, "installProvider: lost the race, "
-                                + "using existing named provider");
-                    }
-                    provider = pr.mProvider;
-                } else {
-                    pr = new ProviderClientRecord(names[i], provider, localProvider);
-                    if (localProvider == null) {
-                        try {
-                            jBinder.linkToDeath(pr, 0);
-                        } catch (RemoteException e) {
-                            // Provider already dead.  Bail out of here without making
-                            // any changes to the provider map or other data structures.
-                            return null;
-                        }
-                    }
-                    mProviderMap.put(names[i], pr);
-                }
-            }
+        IActivityManager.ContentProviderHolder retHolder;
 
+        synchronized (mProviderMap) {
+            if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
+                    + " / " + info.name);
+            IBinder jBinder = provider.asBinder();
             if (localProvider != null) {
-                ProviderClientRecord pr = mLocalProviders.get(jBinder);
+                ComponentName cname = new ComponentName(info.packageName, info.name);
+                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
                 if (pr != null) {
-                    if (localLOGV) {
+                    if (DEBUG_PROVIDER) {
                         Slog.v(TAG, "installProvider: lost the race, "
                                 + "using existing local provider");
                     }
                     provider = pr.mProvider;
                 } else {
-                    pr = new ProviderClientRecord(null, provider, localProvider);
+                    holder = new IActivityManager.ContentProviderHolder(info);
+                    holder.provider = provider;
+                    holder.noReleaseNeeded = true;
+                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
                     mLocalProviders.put(jBinder, pr);
+                    mLocalProvidersByName.put(cname, pr);
                 }
-            }
-
-            if (!noReleaseNeeded) {
+                retHolder = pr.mHolder;
+            } else {
                 ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
                 if (prc != null) {
-                    if (localLOGV) {
-                        Slog.v(TAG, "installProvider: lost the race, incrementing ref count");
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "installProvider: lost the race, updating ref count");
                     }
-                    prc.count += 1;
-                    if (prc.count == 1) {
-                        if (localLOGV) {
-                            Slog.v(TAG, "installProvider: "
-                                    + "snatched provider from the jaws of death");
+                    // We need to transfer our new reference to the existing
+                    // ref count, releasing the old one...  but only if
+                    // release is needed (that is, it is not running in the
+                    // system process).
+                    if (!noReleaseNeeded) {
+                        incProviderRefLocked(prc, stable);
+                        try {
+                            ActivityManagerNative.getDefault().removeContentProvider(
+                                    holder.connection, stable);
+                        } catch (RemoteException e) {
+                            //do nothing content provider object is dead any way
                         }
-                        // Because the provider previously had a reference count of zero,
-                        // it was scheduled to be removed.  Cancel that.
-                        mH.removeMessages(H.REMOVE_PROVIDER, provider);
                     }
                 } else {
-                    mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
+                    ProviderClientRecord client = installProviderAuthoritiesLocked(
+                            provider, localProvider, holder);
+                    if (noReleaseNeeded) {
+                        prc = new ProviderRefCount(holder, client, 1000, 1000);
+                    } else {
+                        prc = stable
+                                ? new ProviderRefCount(holder, client, 1, 0)
+                                : new ProviderRefCount(holder, client, 0, 1);
+                    }
+                    mProviderRefCountMap.put(jBinder, prc);
                 }
+                retHolder = prc.holder;
             }
         }
-        return provider;
+
+        return retHolder;
     }
 
     private void attach(boolean system) {
diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java
index 437362b..3e726e0 100644
--- a/core/java/android/app/ApplicationThreadNative.java
+++ b/core/java/android/app/ApplicationThreadNative.java
@@ -575,6 +575,15 @@
             reply.writeNoException();
             return true;
         }
+
+        case UNSTABLE_PROVIDER_DIED_TRANSACTION:
+        {
+            data.enforceInterface(IApplicationThread.descriptor);
+            IBinder provider = data.readStrongBinder();
+            unstableProviderDied(provider);
+            reply.writeNoException();
+            return true;
+        }
         }
 
         return super.onTransact(code, data, reply, flags);
@@ -1163,4 +1172,12 @@
         mRemote.transact(DUMP_DB_INFO_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
         data.recycle();
     }
+
+    public void unstableProviderDied(IBinder provider) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        data.writeInterfaceToken(IApplicationThread.descriptor);
+        data.writeStrongBinder(provider);
+        mRemote.transact(UNSTABLE_PROVIDER_DIED_TRANSACTION, data, null, IBinder.FLAG_ONEWAY);
+        data.recycle();
+    }
 }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 299e408..4c35a8c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1679,27 +1679,32 @@
 
         @Override
         protected IContentProvider acquireProvider(Context context, String name) {
-            return mMainThread.acquireProvider(context, name);
+            return mMainThread.acquireProvider(context, name, true);
         }
 
         @Override
         protected IContentProvider acquireExistingProvider(Context context, String name) {
-            return mMainThread.acquireExistingProvider(context, name);
+            return mMainThread.acquireExistingProvider(context, name, true);
         }
 
         @Override
         public boolean releaseProvider(IContentProvider provider) {
-            return mMainThread.releaseProvider(provider);
+            return mMainThread.releaseProvider(provider, true);
         }
 
         @Override
         protected IContentProvider acquireUnstableProvider(Context c, String name) {
-            return mMainThread.acquireUnstableProvider(c, name);
+            return mMainThread.acquireProvider(c, name, false);
         }
 
         @Override
         public boolean releaseUnstableProvider(IContentProvider icp) {
-            return mMainThread.releaseUnstableProvider(icp);
+            return mMainThread.releaseProvider(icp, false);
+        }
+
+        @Override
+        public void unstableProviderDied(IContentProvider icp) {
+            mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
         }
 
         private final ActivityThread mMainThread;
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index cf304df..609a047 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -116,14 +116,16 @@
     public void reportThumbnail(IBinder token,
             Bitmap thumbnail, CharSequence description) throws RemoteException;
     public ContentProviderHolder getContentProvider(IApplicationThread caller,
-            String name) throws RemoteException;
+            String name, boolean stable) throws RemoteException;
     public ContentProviderHolder getContentProviderExternal(String name, IBinder token)
             throws RemoteException;
-    public void removeContentProvider(IApplicationThread caller,
-            String name) throws RemoteException;
+    public void removeContentProvider(IBinder connection, boolean stable) throws RemoteException;
     public void removeContentProviderExternal(String name, IBinder token) throws RemoteException;
     public void publishContentProviders(IApplicationThread caller,
             List<ContentProviderHolder> providers) throws RemoteException;
+    public boolean refContentProvider(IBinder connection, int stableDelta, int unstableDelta)
+            throws RemoteException;
+    public void unstableProviderDied(IBinder connection) throws RemoteException;
     public PendingIntent getRunningServiceControlPanel(ComponentName service)
             throws RemoteException;
     public ComponentName startService(IApplicationThread caller, Intent service,
@@ -363,6 +365,7 @@
     public static class ContentProviderHolder implements Parcelable {
         public final ProviderInfo info;
         public IContentProvider provider;
+        public IBinder connection;
         public boolean noReleaseNeeded;
 
         public ContentProviderHolder(ProviderInfo _info) {
@@ -380,6 +383,7 @@
             } else {
                 dest.writeStrongBinder(null);
             }
+            dest.writeStrongBinder(connection);
             dest.writeInt(noReleaseNeeded ? 1:0);
         }
 
@@ -398,6 +402,7 @@
             info = ProviderInfo.CREATOR.createFromParcel(source);
             provider = ContentProviderNative.asInterface(
                 source.readStrongBinder());
+            connection = source.readStrongBinder();
             noReleaseNeeded = source.readInt() != 0;
         }
     }
@@ -476,7 +481,7 @@
     int REPORT_THUMBNAIL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+27;
     int GET_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28;
     int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29;
-    
+    int REF_CONTENT_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30;
     int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31;
     int GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32;
     int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33;
@@ -597,4 +602,5 @@
     int SET_LOCK_SCREEN_SHOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+147;
     int FINISH_ACTIVITY_AFFINITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+148;
     int GET_LAUNCHED_FROM_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+149;
+    int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+150;
 }
diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java
index 70029d2..f60cfd6 100644
--- a/core/java/android/app/IApplicationThread.java
+++ b/core/java/android/app/IApplicationThread.java
@@ -128,6 +128,7 @@
             String[] args) throws RemoteException;
     void dumpGfxInfo(FileDescriptor fd, String[] args) throws RemoteException;
     void dumpDbInfo(FileDescriptor fd, String[] args) throws RemoteException;
+    void unstableProviderDied(IBinder provider) throws RemoteException;
 
     String descriptor = "android.app.IApplicationThread";
 
@@ -176,4 +177,5 @@
     int DUMP_GFX_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+43;
     int DUMP_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+44;
     int DUMP_DB_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+45;
+    int UNSTABLE_PROVIDER_DIED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+46;
 }
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 75c6e11..cad4b01 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -987,7 +987,12 @@
     /**
      * Perform calling of the application's {@link Application#onCreate}
      * method.  The default implementation simply calls through to that method.
-     * 
+     *
+     * <p>Note: This method will be called immediately after {@link #onCreate(Bundle)}.
+     * Often instrumentation tests start their test thread in onCreate(); you
+     * need to be careful of races between these.  (Well between it and
+     * everything else, but let's start here.)
+     *
      * @param app The application being created.
      */
     public void callApplicationOnCreate(Application app) {
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 423f1f6..5c315ce 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -20,6 +20,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.DeadObjectException;
 import android.os.ICancellationSignal;
 import android.os.RemoteException;
 import android.os.ParcelFileDescriptor;
@@ -33,11 +34,19 @@
  * calling {@link ContentResolver#acquireContentProviderClient}. This object must be released
  * using {@link #release} in order to indicate to the system that the {@link ContentProvider} is
  * no longer needed and can be killed to free up resources.
+ *
+ * <p>Note that you should generally create a new ContentProviderClient instance
+ * for each thread that will be performing operations.  Unlike
+ * {@link ContentResolver}, the methods here such as {@link #query} and
+ * {@link #openFile} are not thread safe -- you must not call
+ * {@link #release()} on the ContentProviderClient those calls are made from
+ * until you are finished with the data they have returned.
  */
 public class ContentProviderClient {
     private final IContentProvider mContentProvider;
     private final ContentResolver mContentResolver;
     private final boolean mStable;
+    private boolean mReleased;
 
     /**
      * @hide
@@ -52,7 +61,14 @@
     /** See {@link ContentProvider#query ContentProvider.query} */
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) throws RemoteException {
-        return query(url, projection, selection,  selectionArgs, sortOrder, null);
+        try {
+            return query(url, projection, selection,  selectionArgs, sortOrder, null);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#query ContentProvider.query} */
@@ -64,41 +80,90 @@
             remoteCancellationSignal = mContentProvider.createCancellationSignal();
             cancellationSignal.setRemote(remoteCancellationSignal);
         }
-        return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder,
-                remoteCancellationSignal);
+        try {
+            return mContentProvider.query(url, projection, selection,  selectionArgs, sortOrder,
+                    remoteCancellationSignal);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#getType ContentProvider.getType} */
     public String getType(Uri url) throws RemoteException {
-        return mContentProvider.getType(url);
+        try {
+            return mContentProvider.getType(url);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
-        return mContentProvider.getStreamTypes(url, mimeTypeFilter);
+        try {
+            return mContentProvider.getStreamTypes(url, mimeTypeFilter);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#insert ContentProvider.insert} */
     public Uri insert(Uri url, ContentValues initialValues)
             throws RemoteException {
-        return mContentProvider.insert(url, initialValues);
+        try {
+            return mContentProvider.insert(url, initialValues);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
-        return mContentProvider.bulkInsert(url, initialValues);
+        try {
+            return mContentProvider.bulkInsert(url, initialValues);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#delete ContentProvider.delete} */
     public int delete(Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
-        return mContentProvider.delete(url, selection, selectionArgs);
+        try {
+            return mContentProvider.delete(url, selection, selectionArgs);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#update ContentProvider.update} */
     public int update(Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
-        return mContentProvider.update(url, values, selection, selectionArgs);
+        try {
+            return mContentProvider.update(url, values, selection, selectionArgs);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /**
@@ -110,7 +175,14 @@
      */
     public ParcelFileDescriptor openFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
-        return mContentProvider.openFile(url, mode);
+        try {
+            return mContentProvider.openFile(url, mode);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /**
@@ -122,20 +194,41 @@
      */
     public AssetFileDescriptor openAssetFile(Uri url, String mode)
             throws RemoteException, FileNotFoundException {
-        return mContentProvider.openAssetFile(url, mode);
+        try {
+            return mContentProvider.openAssetFile(url, mode);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#openTypedAssetFile ContentProvider.openTypedAssetFile} */
     public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
             String mimeType, Bundle opts)
             throws RemoteException, FileNotFoundException {
-        return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+        try {
+            return mContentProvider.openTypedAssetFile(uri, mimeType, opts);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
-        return mContentProvider.applyBatch(operations);
+        try {
+            return mContentProvider.applyBatch(operations);
+        } catch (DeadObjectException e) {
+            if (!mStable) {
+                mContentResolver.unstableProviderDied(mContentProvider);
+            }
+            throw e;
+        }
     }
 
     /**
@@ -144,10 +237,16 @@
      * @return true if this was release, false if it was already released
      */
     public boolean release() {
-        if (mStable) {
-            return mContentResolver.releaseProvider(mContentProvider);
-        } else {
-            return mContentResolver.releaseUnstableProvider(mContentProvider);
+        synchronized (this) {
+            if (mReleased) {
+                throw new IllegalStateException("Already released");
+            }
+            mReleased = true;
+            if (mStable) {
+                return mContentResolver.releaseProvider(mContentProvider);
+            } else {
+                return mContentResolver.releaseUnstableProvider(mContentProvider);
+            }
         }
     }
 
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index f509fd8..34b5a30 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -20,27 +20,24 @@
 
 import android.accounts.Account;
 import android.app.ActivityManagerNative;
-import android.app.ActivityThread;
 import android.app.AppGlobals;
-import android.content.ContentProvider.Transport;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.database.CrossProcessCursorWrapper;
 import android.database.Cursor;
-import android.database.CursorWrapper;
 import android.database.IContentObserver;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
+import android.os.DeadObjectException;
 import android.os.IBinder;
 import android.os.ICancellationSignal;
 import android.os.OperationCanceledException;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.StrictMode;
 import android.os.SystemClock;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -202,6 +199,8 @@
     protected abstract IContentProvider acquireUnstableProvider(Context c, String name);
     /** @hide */
     public abstract boolean releaseUnstableProvider(IContentProvider icp);
+    /** @hide */
+    public abstract void unstableProviderDied(IContentProvider icp);
 
     /**
      * Return the MIME type of the given content URL.
@@ -211,6 +210,7 @@
      * @return A MIME type for the content, or null if the URL is invalid or the type is unknown
      */
     public final String getType(Uri url) {
+        // XXX would like to have an acquireExistingUnstableProvider for this.
         IContentProvider provider = acquireExistingProvider(url);
         if (provider != null) {
             try {
@@ -351,23 +351,37 @@
     public final Cursor query(final Uri uri, String[] projection,
             String selection, String[] selectionArgs, String sortOrder,
             CancellationSignal cancellationSignal) {
-        IContentProvider provider = acquireProvider(uri);
-        if (provider == null) {
+        IContentProvider unstableProvider = acquireUnstableProvider(uri);
+        if (unstableProvider == null) {
             return null;
         }
+        IContentProvider stableProvider = null;
         try {
             long startTime = SystemClock.uptimeMillis();
 
             ICancellationSignal remoteCancellationSignal = null;
             if (cancellationSignal != null) {
                 cancellationSignal.throwIfCanceled();
-                remoteCancellationSignal = provider.createCancellationSignal();
+                remoteCancellationSignal = unstableProvider.createCancellationSignal();
                 cancellationSignal.setRemote(remoteCancellationSignal);
             }
-            Cursor qCursor = provider.query(uri, projection,
-                    selection, selectionArgs, sortOrder, remoteCancellationSignal);
+            Cursor qCursor;
+            try {
+                qCursor = unstableProvider.query(uri, projection,
+                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
+            } catch (DeadObjectException e) {
+                // The remote process has died...  but we only hold an unstable
+                // reference though, so we might recover!!!  Let's try!!!!
+                // This is exciting!!1!!1!!!!1
+                unstableProviderDied(unstableProvider);
+                stableProvider = acquireProvider(uri);
+                if (stableProvider == null) {
+                    return null;
+                }
+                qCursor = stableProvider.query(uri, projection,
+                        selection, selectionArgs, sortOrder, remoteCancellationSignal);
+            }
             if (qCursor == null) {
-                releaseProvider(provider);
                 return null;
             }
             // force query execution
@@ -375,16 +389,21 @@
             long durationMillis = SystemClock.uptimeMillis() - startTime;
             maybeLogQueryToEventLog(durationMillis, uri, projection, selection, sortOrder);
             // Wrap the cursor object into CursorWrapperInner object
-            return new CursorWrapperInner(qCursor, provider);
+            CursorWrapperInner wrapper = new CursorWrapperInner(qCursor,
+                    stableProvider != null ? stableProvider : acquireProvider(uri));
+            stableProvider = null;
+            return wrapper;
         } catch (RemoteException e) {
-            releaseProvider(provider);
-
             // Arbitrary and not worth documenting, as Activity
             // Manager will kill this process shortly anyway.
             return null;
-        } catch (RuntimeException e) {
-            releaseProvider(provider);
-            throw e;
+        } finally {
+            if (unstableProvider != null) {
+                releaseUnstableProvider(unstableProvider);
+            }
+            if (stableProvider != null) {
+                releaseProvider(stableProvider);
+            }
         }
     }
 
@@ -592,49 +611,63 @@
             if ("r".equals(mode)) {
                 return openTypedAssetFileDescriptor(uri, "*/*", null);
             } else {
-                int n = 0;
-                while (true) {
-                    n++;
-                    IContentProvider provider = acquireUnstableProvider(uri);
-                    if (provider == null) {
-                        throw new FileNotFoundException("No content provider: " + uri);
-                    }
+                IContentProvider unstableProvider = acquireUnstableProvider(uri);
+                if (unstableProvider == null) {
+                    throw new FileNotFoundException("No content provider: " + uri);
+                }
+                IContentProvider stableProvider = null;
+                AssetFileDescriptor fd = null;
+
+                try {
                     try {
-                        AssetFileDescriptor fd = provider.openAssetFile(uri, mode);
+                        fd = unstableProvider.openAssetFile(uri, mode);
                         if (fd == null) {
                             // The provider will be released by the finally{} clause
                             return null;
                         }
-                        ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
-                                fd.getParcelFileDescriptor(), provider);
-
-                        // Success!  Don't release the provider when exiting, let
-                        // ParcelFileDescriptorInner do that when it is closed.
-                        provider = null;
-
-                        return new AssetFileDescriptor(pfd, fd.getStartOffset(),
-                                fd.getDeclaredLength());
-                    } catch (RemoteException e) {
-                        // The provider died for some reason.  Since we are
-                        // acquiring it unstable, its process could have gotten
-                        // killed and need to be restarted.  We'll retry a couple
-                        // times and if still can't succeed then fail.
-                        if (n <= 2) {
-                            try {
-                                Thread.sleep(100);
-                            } catch (InterruptedException e1) {
-                            }
-                            continue;
+                    } catch (DeadObjectException e) {
+                        // The remote process has died...  but we only hold an unstable
+                        // reference though, so we might recover!!!  Let's try!!!!
+                        // This is exciting!!1!!1!!!!1
+                        unstableProviderDied(unstableProvider);
+                        stableProvider = acquireProvider(uri);
+                        if (stableProvider == null) {
+                            throw new FileNotFoundException("No content provider: " + uri);
                         }
-                        // Whatever, whatever, we'll go away.
-                        throw new FileNotFoundException("Dead content provider: " + uri);
-                    } catch (FileNotFoundException e) {
-                        throw e;
-                    } finally {
-                        if (provider != null) {
-                            releaseUnstableProvider(provider);
+                        fd = stableProvider.openAssetFile(uri, mode);
+                        if (fd == null) {
+                            // The provider will be released by the finally{} clause
+                            return null;
                         }
                     }
+
+                    if (stableProvider == null) {
+                        stableProvider = acquireProvider(uri);
+                    }
+                    releaseUnstableProvider(unstableProvider);
+                    ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                            fd.getParcelFileDescriptor(), stableProvider);
+
+                    // Success!  Don't release the provider when exiting, let
+                    // ParcelFileDescriptorInner do that when it is closed.
+                    stableProvider = null;
+
+                    return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                            fd.getDeclaredLength());
+
+                } catch (RemoteException e) {
+                    // Whatever, whatever, we'll go away.
+                    throw new FileNotFoundException(
+                            "Failed opening content provider: " + uri);
+                } catch (FileNotFoundException e) {
+                    throw e;
+                } finally {
+                    if (stableProvider != null) {
+                        releaseProvider(stableProvider);
+                    }
+                    if (unstableProvider != null) {
+                        releaseUnstableProvider(unstableProvider);
+                    }
                 }
             }
         }
@@ -670,49 +703,63 @@
      */
     public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
             String mimeType, Bundle opts) throws FileNotFoundException {
-        int n = 0;
-        while (true) {
-            n++;
-            IContentProvider provider = acquireUnstableProvider(uri);
-            if (provider == null) {
-                throw new FileNotFoundException("No content provider: " + uri);
-            }
+        IContentProvider unstableProvider = acquireUnstableProvider(uri);
+        if (unstableProvider == null) {
+            throw new FileNotFoundException("No content provider: " + uri);
+        }
+        IContentProvider stableProvider = null;
+        AssetFileDescriptor fd = null;
+
+        try {
             try {
-                AssetFileDescriptor fd = provider.openTypedAssetFile(uri, mimeType, opts);
+                fd = unstableProvider.openTypedAssetFile(uri, mimeType, opts);
                 if (fd == null) {
                     // The provider will be released by the finally{} clause
                     return null;
                 }
-                ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
-                        fd.getParcelFileDescriptor(), provider);
-
-                // Success!  Don't release the provider when exiting, let
-                // ParcelFileDescriptorInner do that when it is closed.
-                provider = null;
-
-                return new AssetFileDescriptor(pfd, fd.getStartOffset(),
-                        fd.getDeclaredLength());
-            } catch (RemoteException e) {
-                // The provider died for some reason.  Since we are
-                // acquiring it unstable, its process could have gotten
-                // killed and need to be restarted.  We'll retry a couple
-                // times and if still can't succeed then fail.
-                if (n <= 2) {
-                    try {
-                        Thread.sleep(100);
-                    } catch (InterruptedException e1) {
-                    }
-                    continue;
+            } catch (DeadObjectException e) {
+                // The remote process has died...  but we only hold an unstable
+                // reference though, so we might recover!!!  Let's try!!!!
+                // This is exciting!!1!!1!!!!1
+                unstableProviderDied(unstableProvider);
+                stableProvider = acquireProvider(uri);
+                if (stableProvider == null) {
+                    throw new FileNotFoundException("No content provider: " + uri);
                 }
-                // Whatever, whatever, we'll go away.
-                throw new FileNotFoundException("Dead content provider: " + uri);
-            } catch (FileNotFoundException e) {
-                throw e;
-            } finally {
-                if (provider != null) {
-                    releaseUnstableProvider(provider);
+                fd = stableProvider.openTypedAssetFile(uri, mimeType, opts);
+                if (fd == null) {
+                    // The provider will be released by the finally{} clause
+                    return null;
                 }
             }
+
+            if (stableProvider == null) {
+                stableProvider = acquireProvider(uri);
+            }
+            releaseUnstableProvider(unstableProvider);
+            ParcelFileDescriptor pfd = new ParcelFileDescriptorInner(
+                    fd.getParcelFileDescriptor(), stableProvider);
+
+            // Success!  Don't release the provider when exiting, let
+            // ParcelFileDescriptorInner do that when it is closed.
+            stableProvider = null;
+
+            return new AssetFileDescriptor(pfd, fd.getStartOffset(),
+                    fd.getDeclaredLength());
+
+        } catch (RemoteException e) {
+            // Whatever, whatever, we'll go away.
+            throw new FileNotFoundException(
+                    "Failed opening content provider: " + uri);
+        } catch (FileNotFoundException e) {
+            throw e;
+        } finally {
+            if (stableProvider != null) {
+                releaseProvider(stableProvider);
+            }
+            if (unstableProvider != null) {
+                releaseUnstableProvider(unstableProvider);
+            }
         }
     }
 
@@ -1061,7 +1108,7 @@
         if (name == null) {
             return null;
         }
-        return acquireProvider(mContext, name);
+        return acquireUnstableProvider(mContext, name);
     }
 
     /**
@@ -1113,10 +1160,15 @@
      * use it as needed and it won't disappear, even if your process is in the
      * background.  If using this method, you need to take care to deal with any
      * failures when communicating with the provider, and be sure to close it
-     * so that it can be re-opened later.
+     * so that it can be re-opened later.  In particular, catching a
+     * {@link android.os.DeadObjectException} from the calls there will let you
+     * know that the content provider has gone away; at that point the current
+     * ContentProviderClient object is invalid, and you should release it.  You
+     * can acquire a new one if you would like to try to restart the provider
+     * and perform new operations on it.
      */
     public final ContentProviderClient acquireUnstableContentProviderClient(Uri uri) {
-        IContentProvider provider = acquireProvider(uri);
+        IContentProvider provider = acquireUnstableProvider(uri);
         if (provider != null) {
             return new ContentProviderClient(this, provider, false);
         }
@@ -1133,10 +1185,15 @@
      * use it as needed and it won't disappear, even if your process is in the
      * background.  If using this method, you need to take care to deal with any
      * failures when communicating with the provider, and be sure to close it
-     * so that it can be re-opened later.
+     * so that it can be re-opened later.  In particular, catching a
+     * {@link android.os.DeadObjectException} from the calls there will let you
+     * know that the content provider has gone away; at that point the current
+     * ContentProviderClient object is invalid, and you should release it.  You
+     * can acquire a new one if you would like to try to restart the provider
+     * and perform new operations on it.
      */
     public final ContentProviderClient acquireUnstableContentProviderClient(String name) {
-        IContentProvider provider = acquireProvider(name);
+        IContentProvider provider = acquireUnstableProvider(name);
         if (provider != null) {
             return new ContentProviderClient(this, provider, false);
         }
@@ -1780,7 +1837,6 @@
 
     private final class ParcelFileDescriptorInner extends ParcelFileDescriptor {
         private final IContentProvider mContentProvider;
-        public static final String TAG="ParcelFileDescriptorInner";
         private boolean mReleaseProviderFlag = false;
 
         ParcelFileDescriptorInner(ParcelFileDescriptor pfd, IContentProvider icp) {
@@ -1792,7 +1848,7 @@
         public void close() throws IOException {
             if(!mReleaseProviderFlag) {
                 super.close();
-                ContentResolver.this.releaseUnstableProvider(mContentProvider);
+                ContentResolver.this.releaseProvider(mContentProvider);
                 mReleaseProviderFlag = true;
             }
         }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 6464d7f..9d9b5b8 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -62,6 +62,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.IContentProvider;
 import android.content.IIntentReceiver;
 import android.content.IIntentSender;
 import android.content.Intent;
@@ -1810,12 +1811,11 @@
                 }
             }
         }
-        if (app.conProviders.size() > 0) {
-            for (ContentProviderRecord cpr : app.conProviders.keySet()) {
-                if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
-                    updateLruProcessInternalLocked(cpr.proc, oomAdj,
-                            updateActivityTime, i+1);
-                }
+        for (int j=app.conProviders.size()-1; j>=0; j--) {
+            ContentProviderRecord cpr = app.conProviders.get(j).provider;
+            if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) {
+                updateLruProcessInternalLocked(cpr.proc, oomAdj,
+                        updateActivityTime, i+1);
             }
         }
         
@@ -3742,7 +3742,7 @@
 
         N = providers.size();
         for (i=0; i<N; i++) {
-            removeDyingProviderLocked(null, providers.get(i));
+            removeDyingProviderLocked(null, providers.get(i), true);
         }
 
         if (doit) {
@@ -6058,53 +6058,72 @@
         return msg;
     }
 
-    boolean incProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
-            IBinder externalProcessToken) {
+    ContentProviderConnection incProviderCountLocked(ProcessRecord r,
+            final ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
         if (r != null) {
-            Integer cnt = r.conProviders.get(cpr);
-            if (DEBUG_PROVIDER) Slog.v(TAG,
-                    "Adding provider requested by "
-                    + r.processName + " from process "
-                    + cpr.info.processName + ": " + cpr.name.flattenToShortString()
-                    + " cnt=" + (cnt == null ? 1 : cnt));
-            if (cnt == null) {
-                cpr.clients.add(r);
-                r.conProviders.put(cpr, new Integer(1));
-                return true;
-            } else {
-                r.conProviders.put(cpr, new Integer(cnt.intValue()+1));
+            for (int i=0; i<r.conProviders.size(); i++) {
+                ContentProviderConnection conn = r.conProviders.get(i);
+                if (conn.provider == cpr) {
+                    if (DEBUG_PROVIDER) Slog.v(TAG,
+                            "Adding provider requested by "
+                            + r.processName + " from process "
+                            + cpr.info.processName + ": " + cpr.name.flattenToShortString()
+                            + " scnt=" + conn.stableCount + " uscnt=" + conn.unstableCount);
+                    if (stable) {
+                        conn.stableCount++;
+                        conn.numStableIncs++;
+                    } else {
+                        conn.unstableCount++;
+                        conn.numUnstableIncs++;
+                    }
+                    return conn;
+                }
             }
-        } else {
-            cpr.addExternalProcessHandleLocked(externalProcessToken);
+            ContentProviderConnection conn = new ContentProviderConnection(cpr, r);
+            if (stable) {
+                conn.stableCount = 1;
+                conn.numStableIncs = 1;
+            } else {
+                conn.unstableCount = 1;
+                conn.numUnstableIncs = 1;
+            }
+            cpr.connections.add(conn);
+            r.conProviders.add(conn);
+            return conn;
         }
-        return false;
+        cpr.addExternalProcessHandleLocked(externalProcessToken);
+        return null;
     }
 
-    boolean decProviderCount(ProcessRecord r, final ContentProviderRecord cpr,
-            IBinder externalProcessToken) {
-        if (r != null) {
-            Integer cnt = r.conProviders.get(cpr);
+    boolean decProviderCountLocked(ContentProviderConnection conn,
+            ContentProviderRecord cpr, IBinder externalProcessToken, boolean stable) {
+        if (conn != null) {
+            cpr = conn.provider;
             if (DEBUG_PROVIDER) Slog.v(TAG,
                     "Removing provider requested by "
-                    + r.processName + " from process "
+                    + conn.client.processName + " from process "
                     + cpr.info.processName + ": " + cpr.name.flattenToShortString()
-                    + " cnt=" + cnt);
-            if (cnt == null || cnt.intValue() <= 1) {
-                cpr.clients.remove(r);
-                r.conProviders.remove(cpr);
-                return true;
+                    + " scnt=" + conn.stableCount + " uscnt=" + conn.unstableCount);
+            if (stable) {
+                conn.stableCount--;
             } else {
-                r.conProviders.put(cpr, new Integer(cnt.intValue()-1));
+                conn.unstableCount--;
             }
-        } else {
-            cpr.removeExternalProcessHandleLocked(externalProcessToken);
+            if (conn.stableCount == 0 && conn.unstableCount == 0) {
+                cpr.connections.remove(conn);
+                conn.client.conProviders.remove(conn);
+                return true;
+            }
+            return false;
         }
+        cpr.removeExternalProcessHandleLocked(externalProcessToken);
         return false;
     }
 
     private final ContentProviderHolder getContentProviderImpl(IApplicationThread caller,
-            String name, IBinder token) {
+            String name, IBinder token, boolean stable) {
         ContentProviderRecord cpr;
+        ContentProviderConnection conn = null;
         ProviderInfo cpi = null;
 
         synchronized(this) {
@@ -6135,20 +6154,19 @@
                     // of being published...  but it is also allowed to run
                     // in the caller's process, so don't make a connection
                     // and just let the caller instantiate its own instance.
-                    if (cpr.provider != null) {
-                        // don't give caller the provider object, it needs
-                        // to make its own.
-                        cpr = new ContentProviderRecord(cpr);
-                    }
-                    return cpr;
+                    ContentProviderHolder holder = cpr.newHolder(null);
+                    // don't give caller the provider object, it needs
+                    // to make its own.
+                    holder.provider = null;
+                    return holder;
                 }
 
                 final long origId = Binder.clearCallingIdentity();
 
                 // In this case the provider instance already exists, so we can
                 // return it right away.
-                final boolean countChanged = incProviderCount(r, cpr, token);
-                if (countChanged) {
+                conn = incProviderCountLocked(r, cpr, token, stable);
+                if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
                     if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ) {
                         // If this is a perceptible app accessing the provider,
                         // make sure to count it as being accessed and thus
@@ -6181,7 +6199,7 @@
                         Slog.i(TAG,
                                 "Existing provider " + cpr.name.flattenToShortString()
                                 + " is crashing; detaching " + r);
-                        boolean lastRef = decProviderCount(r, cpr, token);
+                        boolean lastRef = decProviderCountLocked(conn, cpr, token, stable);
                         appDiedLocked(cpr.proc, cpr.proc.pid, cpr.proc.thread);
                         if (!lastRef) {
                             // This wasn't the last ref our process had on
@@ -6189,6 +6207,7 @@
                             return null;
                         }
                         providerRunning = false;
+                        conn = null;
                     }
                 }
 
@@ -6251,7 +6270,7 @@
                     // info and allow the caller to instantiate it.  Only do
                     // this if the provider is the same user as the caller's
                     // process, or can run as root (so can be in any process).
-                    return cpr;
+                    return cpr.newHolder(null);
                 }
 
                 if (DEBUG_PROVIDER) {
@@ -6312,7 +6331,10 @@
                 }
 
                 mProviderMap.putProviderByName(name, cpr);
-                incProviderCount(r, cpr, token);
+                conn = incProviderCountLocked(r, cpr, token, stable);
+                if (conn != null) {
+                    conn.waiting = true;
+                }
             }
         }
 
@@ -6334,16 +6356,23 @@
                         Slog.v(TAG_MU, "Waiting to start provider " + cpr + " launchingApp="
                                 + cpr.launchingApp);
                     }
+                    if (conn != null) {
+                        conn.waiting = true;
+                    }
                     cpr.wait();
                 } catch (InterruptedException ex) {
+                } finally {
+                    if (conn != null) {
+                        conn.waiting = false;
+                    }
                 }
             }
         }
-        return cpr;
+        return cpr != null ? cpr.newHolder(conn) : null;
     }
 
     public final ContentProviderHolder getContentProvider(
-            IApplicationThread caller, String name) {
+            IApplicationThread caller, String name, boolean stable) {
         enforceNotIsolatedCaller("getContentProvider");
         if (caller == null) {
             String msg = "null IApplicationThread when getting content provider "
@@ -6352,7 +6381,7 @@
             throw new SecurityException(msg);
         }
 
-        return getContentProviderImpl(caller, name, null);
+        return getContentProviderImpl(caller, name, null, stable);
     }
 
     public ContentProviderHolder getContentProviderExternal(String name, IBinder token) {
@@ -6362,45 +6391,30 @@
     }
 
     private ContentProviderHolder getContentProviderExternalUnchecked(String name,IBinder token) {
-        return getContentProviderImpl(null, name, token);
+        return getContentProviderImpl(null, name, token, true);
     }
 
     /**
      * Drop a content provider from a ProcessRecord's bookkeeping
      * @param cpr
      */
-    public void removeContentProvider(IApplicationThread caller, String name) {
+    public void removeContentProvider(IBinder connection, boolean stable) {
         enforceNotIsolatedCaller("removeContentProvider");
         synchronized (this) {
-            int userId = UserId.getUserId(Binder.getCallingUid());
-            ContentProviderRecord cpr = mProviderMap.getProviderByName(name, userId);
-            if(cpr == null) {
-                // remove from mProvidersByClass
-                if (DEBUG_PROVIDER) Slog.v(TAG, name +
-                        " provider not found in providers list");
-                return;
+            ContentProviderConnection conn;
+            try {
+                conn = (ContentProviderConnection)connection;
+            } catch (ClassCastException e) {
+                String msg ="removeContentProvider: " + connection
+                        + " not a ContentProviderConnection";
+                Slog.w(TAG, msg);
+                throw new IllegalArgumentException(msg);
             }
-            final ProcessRecord r = getRecordForAppLocked(caller);
-            if (r == null) {
-                throw new SecurityException(
-                        "Unable to find app for caller " + caller +
-                        " when removing content provider " + name);
+            if (conn == null) {
+                throw new NullPointerException("connection is null");
             }
-            //update content provider record entry info
-            ComponentName comp = new ComponentName(cpr.info.packageName, cpr.info.name);
-            ContentProviderRecord localCpr = mProviderMap.getProviderByClass(comp, userId);
-            if (DEBUG_PROVIDER) Slog.v(TAG, "Removing provider requested by "
-                    + r.info.processName + " from process "
-                    + localCpr.appInfo.processName);
-            if (localCpr.launchingApp == r) {
-                //should not happen. taken care of as a local provider
-                Slog.w(TAG, "removeContentProvider called on local provider: "
-                        + cpr.info.name + " in process " + r.processName);
-                return;
-            } else {
-                if (decProviderCount(r, localCpr, null)) {
-                    updateOomAdjLocked();
-                }
+            if (decProviderCountLocked(conn, null, null, stable)) {
+                updateOomAdjLocked();
             }
         }
     }
@@ -6447,7 +6461,7 @@
         }
 
         enforceNotIsolatedCaller("publishContentProviders");
-        synchronized(this) {
+        synchronized (this) {
             final ProcessRecord r = getRecordForAppLocked(caller);
             if (DEBUG_MU)
                 Slog.v(TAG_MU, "ProcessRecord uid = " + r.uid);
@@ -6499,6 +6513,103 @@
         }
     }
 
+    public boolean refContentProvider(IBinder connection, int stable, int unstable) {
+        ContentProviderConnection conn;
+        try {
+            conn = (ContentProviderConnection)connection;
+        } catch (ClassCastException e) {
+            String msg ="refContentProvider: " + connection
+                    + " not a ContentProviderConnection";
+            Slog.w(TAG, msg);
+            throw new IllegalArgumentException(msg);
+        }
+        if (conn == null) {
+            throw new NullPointerException("connection is null");
+        }
+
+        synchronized (this) {
+            if (stable > 0) {
+                conn.numStableIncs += stable;
+            }
+            stable = conn.stableCount + stable;
+            if (stable < 0) {
+                throw new IllegalStateException("stableCount < 0: " + stable);
+            }
+
+            if (unstable > 0) {
+                conn.numUnstableIncs += unstable;
+            }
+            unstable = conn.unstableCount + unstable;
+            if (unstable < 0) {
+                throw new IllegalStateException("unstableCount < 0: " + unstable);
+            }
+
+            if ((stable+unstable) <= 0) {
+                throw new IllegalStateException("ref counts can't go to zero here: stable="
+                        + stable + " unstable=" + unstable);
+            }
+            conn.stableCount = stable;
+            conn.unstableCount = unstable;
+            return !conn.dead;
+        }
+    }
+
+    public void unstableProviderDied(IBinder connection) {
+        ContentProviderConnection conn;
+        try {
+            conn = (ContentProviderConnection)connection;
+        } catch (ClassCastException e) {
+            String msg ="refContentProvider: " + connection
+                    + " not a ContentProviderConnection";
+            Slog.w(TAG, msg);
+            throw new IllegalArgumentException(msg);
+        }
+        if (conn == null) {
+            throw new NullPointerException("connection is null");
+        }
+
+        // Safely retrieve the content provider associated with the connection.
+        IContentProvider provider;
+        synchronized (this) {
+            provider = conn.provider.provider;
+        }
+
+        if (provider == null) {
+            // Um, yeah, we're way ahead of you.
+            return;
+        }
+
+        // Make sure the caller is being honest with us.
+        if (provider.asBinder().pingBinder()) {
+            // Er, no, still looks good to us.
+            synchronized (this) {
+                Slog.w(TAG, "unstableProviderDied: caller " + Binder.getCallingUid()
+                        + " says " + conn + " died, but we don't agree");
+                return;
+            }
+        }
+
+        // Well look at that!  It's dead!
+        synchronized (this) {
+            if (conn.provider.provider != provider) {
+                // But something changed...  good enough.
+                return;
+            }
+
+            ProcessRecord proc = conn.provider.proc;
+            if (proc == null || proc.thread == null) {
+                // Seems like the process is already cleaned up.
+                return;
+            }
+
+            // As far as we're concerned, this is just like receiving a
+            // death notification...  just a bit prematurely.
+            Slog.i(TAG, "Process " + proc.processName + " (pid " + proc.pid
+                    + ") early provider death");
+            appDiedLocked(proc, proc.pid, proc.thread);
+        }
+    }
+
     public static final void installSystemProviders() {
         List<ProviderInfo> providers;
         synchronized (mSelf) {
@@ -10515,36 +10626,62 @@
         app.executingServices.clear();
     }
 
-    private final void removeDyingProviderLocked(ProcessRecord proc,
-            ContentProviderRecord cpr) {
-        synchronized (cpr) {
-            cpr.launchingApp = null;
-            cpr.notifyAll();
-        }
-        
-        mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid));
-        String names[] = cpr.info.authority.split(";");
-        for (int j = 0; j < names.length; j++) {
-            mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid));
-        }
-        
-        Iterator<ProcessRecord> cit = cpr.clients.iterator();
-        while (cit.hasNext()) {
-            ProcessRecord capp = cit.next();
-            if (!capp.persistent && capp.thread != null
-                    && capp.pid != 0
-                    && capp.pid != MY_PID) {
-                Slog.i(TAG, "Kill " + capp.processName
-                        + " (pid " + capp.pid + "): provider " + cpr.info.name
-                        + " in dying process " + (proc != null ? proc.processName : "??"));
-                EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
-                        capp.processName, capp.setAdj, "dying provider "
-                                + cpr.name.toShortString());
-                Process.killProcessQuiet(capp.pid);
+    private final boolean removeDyingProviderLocked(ProcessRecord proc,
+            ContentProviderRecord cpr, boolean always) {
+        final boolean inLaunching = mLaunchingProviders.contains(cpr);
+
+        if (!inLaunching || always) {
+            synchronized (cpr) {
+                cpr.launchingApp = null;
+                cpr.notifyAll();
+            }
+            mProviderMap.removeProviderByClass(cpr.name, UserId.getUserId(cpr.uid));
+            String names[] = cpr.info.authority.split(";");
+            for (int j = 0; j < names.length; j++) {
+                mProviderMap.removeProviderByName(names[j], UserId.getUserId(cpr.uid));
             }
         }
-        
-        mLaunchingProviders.remove(cpr);
+
+        for (int i=0; i<cpr.connections.size(); i++) {
+            ContentProviderConnection conn = cpr.connections.get(i);
+            if (conn.waiting) {
+                // If this connection is waiting for the provider, then we don't
+                // need to mess with its process unless we are always removing
+                // or for some reason the provider is not currently launching.
+                if (inLaunching && !always) {
+                    continue;
+                }
+            }
+            ProcessRecord capp = conn.client;
+            conn.dead = true;
+            if (conn.stableCount > 0) {
+                if (!capp.persistent && capp.thread != null
+                        && capp.pid != 0
+                        && capp.pid != MY_PID) {
+                    Slog.i(TAG, "Kill " + capp.processName
+                            + " (pid " + capp.pid + "): provider " + cpr.info.name
+                            + " in dying process " + (proc != null ? proc.processName : "??"));
+                    EventLog.writeEvent(EventLogTags.AM_KILL, capp.pid,
+                            capp.processName, capp.setAdj, "dying provider "
+                                    + cpr.name.toShortString());
+                    Process.killProcessQuiet(capp.pid);
+                }
+            } else if (capp.thread != null && conn.provider.provider != null) {
+                try {
+                    capp.thread.unstableProviderDied(conn.provider.provider.asBinder());
+                } catch (RemoteException e) {
+                }
+                // In the protocol here, we don't expect the client to correctly
+                // clean up this connection, we'll just remove it.
+                cpr.connections.remove(i);
+                conn.client.conProviders.remove(conn);
+            }
+        }
+
+        if (inLaunching && always) {
+            mLaunchingProviders.remove(cpr);
+        }
+        return inLaunching;
     }
     
     /**
@@ -10590,34 +10727,21 @@
 
         boolean restart = false;
 
-        int NL = mLaunchingProviders.size();
-        
         // Remove published content providers.
         if (!app.pubProviders.isEmpty()) {
             Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator();
             while (it.hasNext()) {
                 ContentProviderRecord cpr = it.next();
+
+                final boolean always = app.bad || !allowRestart;
+                if (removeDyingProviderLocked(app, cpr, always) || always) {
+                    // We left the provider in the launching list, need to
+                    // restart it.
+                    restart = true;
+                }
+
                 cpr.provider = null;
                 cpr.proc = null;
-
-                // See if someone is waiting for this provider...  in which
-                // case we don't remove it, but just let it restart.
-                int i = 0;
-                if (!app.bad && allowRestart) {
-                    for (; i<NL; i++) {
-                        if (mLaunchingProviders.get(i) == cpr) {
-                            restart = true;
-                            break;
-                        }
-                    }
-                } else {
-                    i = NL;
-                }
-
-                if (i >= NL) {
-                    removeDyingProviderLocked(app, cpr);
-                    NL = mLaunchingProviders.size();
-                }
             }
             app.pubProviders.clear();
         }
@@ -10629,10 +10753,9 @@
         
         // Unregister from connected content providers.
         if (!app.conProviders.isEmpty()) {
-            Iterator it = app.conProviders.keySet().iterator();
-            while (it.hasNext()) {
-                ContentProviderRecord cpr = (ContentProviderRecord)it.next();
-                cpr.clients.remove(app);
+            for (int i=0; i<app.conProviders.size(); i++) {
+                ContentProviderConnection conn = app.conProviders.get(i);
+                conn.provider.connections.remove(conn);
             }
             app.conProviders.clear();
         }
@@ -10643,10 +10766,10 @@
         // XXX Commented out for now.  Trying to figure out a way to reproduce
         // the actual situation to identify what is actually going on.
         if (false) {
-            for (int i=0; i<NL; i++) {
+            for (int i=0; i<mLaunchingProviders.size(); i++) {
                 ContentProviderRecord cpr = (ContentProviderRecord)
                         mLaunchingProviders.get(i);
-                if (cpr.clients.size() <= 0 && !cpr.hasExternalProcessHandles()) {
+                if (cpr.connections.size() <= 0 && !cpr.hasExternalProcessHandles()) {
                     synchronized (cpr) {
                         cpr.launchingApp = null;
                         cpr.notifyAll();
@@ -10743,7 +10866,7 @@
                 if (!alwaysBad && !app.bad) {
                     restart = true;
                 } else {
-                    removeDyingProviderLocked(app, cpr);
+                    removeDyingProviderLocked(app, cpr, true);
                     NL = mLaunchingProviders.size();
                 }
             }
@@ -13948,48 +14071,49 @@
             while (jt.hasNext() && (adj > ProcessList.FOREGROUND_APP_ADJ
                     || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) {
                 ContentProviderRecord cpr = jt.next();
-                if (cpr.clients.size() != 0) {
-                    Iterator<ProcessRecord> kt = cpr.clients.iterator();
-                    while (kt.hasNext() && adj > ProcessList.FOREGROUND_APP_ADJ) {
-                        ProcessRecord client = kt.next();
-                        if (client == app) {
-                            // Being our own client is not interesting.
-                            continue;
+                for (int i = cpr.connections.size()-1;
+                        i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
+                                || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE);
+                        i--) {
+                    ContentProviderConnection conn = cpr.connections.get(i);
+                    ProcessRecord client = conn.client;
+                    if (client == app) {
+                        // Being our own client is not interesting.
+                        continue;
+                    }
+                    int myHiddenAdj = hiddenAdj;
+                    if (myHiddenAdj > client.hiddenAdj) {
+                        if (client.hiddenAdj > ProcessList.FOREGROUND_APP_ADJ) {
+                            myHiddenAdj = client.hiddenAdj;
+                        } else {
+                            myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ;
                         }
-                        int myHiddenAdj = hiddenAdj;
-                        if (myHiddenAdj > client.hiddenAdj) {
-                            if (client.hiddenAdj > ProcessList.FOREGROUND_APP_ADJ) {
-                                myHiddenAdj = client.hiddenAdj;
-                            } else {
-                                myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ;
-                            }
+                    }
+                    int clientAdj = computeOomAdjLocked(
+                        client, myHiddenAdj, TOP_APP, true, doingAll);
+                    if (adj > clientAdj) {
+                        if (app.hasShownUi && app != mHomeProcess
+                                && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+                            app.adjType = "bg-ui-provider";
+                        } else {
+                            adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
+                                    ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
+                            app.adjType = "provider";
                         }
-                        int clientAdj = computeOomAdjLocked(
-                            client, myHiddenAdj, TOP_APP, true, doingAll);
-                        if (adj > clientAdj) {
-                            if (app.hasShownUi && app != mHomeProcess
-                                    && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) {
-                                app.adjType = "bg-ui-provider";
-                            } else {
-                                adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ
-                                        ? clientAdj : ProcessList.FOREGROUND_APP_ADJ;
-                                app.adjType = "provider";
-                            }
-                            if (!client.hidden) {
-                                app.hidden = false;
-                            }
-                            if (client.keeping) {
-                                app.keeping = true;
-                            }
-                            app.adjTypeCode = ActivityManager.RunningAppProcessInfo
-                                    .REASON_PROVIDER_IN_USE;
-                            app.adjSource = client;
-                            app.adjSourceOom = clientAdj;
-                            app.adjTarget = cpr.name;
+                        if (!client.hidden) {
+                            app.hidden = false;
                         }
-                        if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
-                            schedGroup = Process.THREAD_GROUP_DEFAULT;
+                        if (client.keeping) {
+                            app.keeping = true;
                         }
+                        app.adjTypeCode = ActivityManager.RunningAppProcessInfo
+                                .REASON_PROVIDER_IN_USE;
+                        app.adjSource = client;
+                        app.adjSourceOom = clientAdj;
+                        app.adjTarget = cpr.name;
+                    }
+                    if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) {
+                        schedGroup = Process.THREAD_GROUP_DEFAULT;
                     }
                 }
                 // If the provider has external (non-framework) process
diff --git a/services/java/com/android/server/am/ContentProviderConnection.java b/services/java/com/android/server/am/ContentProviderConnection.java
new file mode 100644
index 0000000..84f8f02
--- /dev/null
+++ b/services/java/com/android/server/am/ContentProviderConnection.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.os.Binder;
+
+/**
+ * Represents a link between a content provider and client.
+ */
+public class ContentProviderConnection extends Binder {
+    public final ContentProviderRecord provider;
+    public final ProcessRecord client;
+    public int stableCount;
+    public int unstableCount;
+    // The client of this connection is currently waiting for the provider to appear.
+    // Protected by the provider lock.
+    public boolean waiting;
+    // The provider of this connection is now dead.
+    public boolean dead;
+
+    // For debugging.
+    public int numStableIncs;
+    public int numUnstableIncs;
+
+    public ContentProviderConnection(ContentProviderRecord _provider, ProcessRecord _client) {
+        provider = _provider;
+        client = _client;
+    }
+
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("ContentProviderConnection{");
+        toShortString(sb);
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public String toShortString() {
+        StringBuilder sb = new StringBuilder(128);
+        toShortString(sb);
+        return sb.toString();
+    }
+
+    public String toClientString() {
+        StringBuilder sb = new StringBuilder(128);
+        toClientString(sb);
+        return sb.toString();
+    }
+
+    public void toShortString(StringBuilder sb) {
+        sb.append(provider.toShortString());
+        sb.append("->");
+        toClientString(sb);
+    }
+
+    public void toClientString(StringBuilder sb) {
+        sb.append(client.toShortString());
+        sb.append(" s");
+        sb.append(stableCount);
+        sb.append("/");
+        sb.append(numStableIncs);
+        sb.append(" u");
+        sb.append(unstableCount);
+        sb.append("/");
+        sb.append(numUnstableIncs);
+        if (waiting) {
+            sb.append(" WAITING");
+        }
+        if (dead) {
+            sb.append(" DEAD");
+        }
+    }
+}
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index 608b09a..fb21b06 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -18,6 +18,7 @@
 
 import android.app.IActivityManager.ContentProviderHolder;
 import android.content.ComponentName;
+import android.content.IContentProvider;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ProviderInfo;
 import android.os.IBinder;
@@ -27,28 +28,35 @@
 import android.util.Slog;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 
-class ContentProviderRecord extends ContentProviderHolder {
+class ContentProviderRecord {
+    final ActivityManagerService service;
+    public final ProviderInfo info;
+    final int uid;
+    final ApplicationInfo appInfo;
+    final ComponentName name;
+    public IContentProvider provider;
+    public boolean noReleaseNeeded;
     // All attached clients
-    final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
+    final ArrayList<ContentProviderConnection> connections
+            = new ArrayList<ContentProviderConnection>();
+    //final HashSet<ProcessRecord> clients = new HashSet<ProcessRecord>();
     // Handles for non-framework processes supported by this provider
     HashMap<IBinder, ExternalProcessHandle> externalProcessTokenToHandle;
     // Count for external process for which we have no handles.
     int externalProcessNoHandleCount;
-    final ActivityManagerService service;
-    final int uid;
-    final ApplicationInfo appInfo;
-    final ComponentName name;
     ProcessRecord proc; // if non-null, hosting process.
     ProcessRecord launchingApp; // if non-null, waiting for this app to be launched.
     String stringName;
+    String shortStringName;
 
     public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info,
             ApplicationInfo ai, ComponentName _name) {
-        super(_info);
         service = _service;
+        info = _info;
         uid = ai.uid;
         appInfo = ai;
         name = _name;
@@ -56,12 +64,20 @@
     }
 
     public ContentProviderRecord(ContentProviderRecord cpr) {
-        super(cpr.info);
+        service = cpr.service;
+        info = cpr.info;
         uid = cpr.uid;
         appInfo = cpr.appInfo;
         name = cpr.name;
         noReleaseNeeded = cpr.noReleaseNeeded;
-        service = cpr.service;
+    }
+
+    public ContentProviderHolder newHolder(ContentProviderConnection conn) {
+        ContentProviderHolder holder = new ContentProviderHolder(info);
+        holder.provider = provider;
+        holder.noReleaseNeeded = noReleaseNeeded;
+        holder.connection = conn;
+        return holder;
     }
 
     public boolean canRunHere(ProcessRecord app) {
@@ -120,30 +136,51 @@
         return (externalProcessTokenToHandle != null || externalProcessNoHandleCount > 0);
     }
 
-    void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("package=");
-                pw.print(info.applicationInfo.packageName);
-                pw.print(" process="); pw.println(info.processName);
+    void dump(PrintWriter pw, String prefix, boolean full) {
+        if (full) {
+            pw.print(prefix); pw.print("package=");
+                    pw.print(info.applicationInfo.packageName);
+                    pw.print(" process="); pw.println(info.processName);
+        }
         pw.print(prefix); pw.print("proc="); pw.println(proc);
         if (launchingApp != null) {
             pw.print(prefix); pw.print("launchingApp="); pw.println(launchingApp);
         }
-        pw.print(prefix); pw.print("uid="); pw.print(uid);
-                pw.print(" provider="); pw.println(provider);
-        pw.print(prefix); pw.print("name="); pw.println(info.authority);
-        if (info.isSyncable || info.multiprocess || info.initOrder != 0) {
-            pw.print(prefix); pw.print("isSyncable="); pw.print(info.isSyncable);
-                    pw.print("multiprocess="); pw.print(info.multiprocess);
-                    pw.print(" initOrder="); pw.println(info.initOrder);
+        if (full) {
+            pw.print(prefix); pw.print("uid="); pw.print(uid);
+                    pw.print(" provider="); pw.println(provider);
         }
-        if (hasExternalProcessHandles()) {
-            pw.print(prefix); pw.print("externals=");
-            pw.println(externalProcessTokenToHandle.size());
+        pw.print(prefix); pw.print("authority="); pw.println(info.authority);
+        if (full) {
+            if (info.isSyncable || info.multiprocess || info.initOrder != 0) {
+                pw.print(prefix); pw.print("isSyncable="); pw.print(info.isSyncable);
+                        pw.print(" multiprocess="); pw.print(info.multiprocess);
+                        pw.print(" initOrder="); pw.println(info.initOrder);
+            }
         }
-        if (clients.size() > 0) {
-            pw.print(prefix); pw.println("Clients:");
-            for (ProcessRecord cproc : clients) {
-                pw.print(prefix); pw.print("  - "); pw.println(cproc.toShortString());
+        if (full) {
+            if (hasExternalProcessHandles()) {
+                pw.print(prefix); pw.print("externals=");
+                        pw.println(externalProcessTokenToHandle.size());
+            }
+        } else {
+            if (connections.size() > 0 || externalProcessNoHandleCount > 0) {
+                pw.print(prefix); pw.print(connections.size());
+                        pw.print(" connections, "); pw.print(externalProcessNoHandleCount);
+                        pw.println(" external handles");
+            }
+        }
+        if (connections.size() > 0) {
+            if (full) {
+                pw.print(prefix); pw.println("Connections:");
+            }
+            for (int i=0; i<connections.size(); i++) {
+                ContentProviderConnection conn = connections.get(i);
+                pw.print(prefix); pw.print("  -> "); pw.println(conn.toClientString());
+                if (conn.provider != this) {
+                    pw.print(prefix); pw.print("    *** WRONG PROVIDER: ");
+                            pw.println(conn.provider);
+                }
             }
         }
     }
@@ -162,6 +199,17 @@
         return stringName = sb.toString();
     }
 
+    public String toShortString() {
+        if (shortStringName != null) {
+            return shortStringName;
+        }
+        StringBuilder sb = new StringBuilder(128);
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        sb.append('/');
+        sb.append(name.flattenToShortString());
+        return shortStringName = sb.toString();
+    }
+
     // This class represents a handle from an external process to a provider.
     private class ExternalProcessHandle implements DeathRecipient {
         private static final String LOG_TAG = "ExternalProcessHanldle";
diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java
index 4529ecc..8a3ba7f 100644
--- a/services/java/com/android/server/am/ProcessRecord.java
+++ b/services/java/com/android/server/am/ProcessRecord.java
@@ -125,8 +125,8 @@
     final HashMap<String, ContentProviderRecord> pubProviders
             = new HashMap<String, ContentProviderRecord>(); 
     // All ContentProviderRecord process is using
-    final HashMap<ContentProviderRecord, Integer> conProviders
-            = new HashMap<ContentProviderRecord, Integer>(); 
+    final ArrayList<ContentProviderConnection> conProviders
+            = new ArrayList<ContentProviderConnection>();
     
     boolean persistent;         // always keep this application running?
     boolean crashing;           // are we in the process of crashing?
@@ -257,25 +257,47 @@
                     pw.println();
         }
         if (activities.size() > 0) {
-            pw.print(prefix); pw.print("activities="); pw.println(activities);
+            pw.print(prefix); pw.println("Activities:");
+            for (int i=0; i<activities.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(activities.get(i));
+            }
         }
         if (services.size() > 0) {
-            pw.print(prefix); pw.print("services="); pw.println(services);
+            pw.print(prefix); pw.println("Services:");
+            for (ServiceRecord sr : services) {
+                pw.print(prefix); pw.print("  - "); pw.println(sr);
+            }
         }
         if (executingServices.size() > 0) {
-            pw.print(prefix); pw.print("executingServices="); pw.println(executingServices);
+            pw.print(prefix); pw.println("Executing Services:");
+            for (ServiceRecord sr : executingServices) {
+                pw.print(prefix); pw.print("  - "); pw.println(sr);
+            }
         }
         if (connections.size() > 0) {
-            pw.print(prefix); pw.print("connections="); pw.println(connections);
+            pw.print(prefix); pw.println("Connections:");
+            for (ConnectionRecord cr : connections) {
+                pw.print(prefix); pw.print("  - "); pw.println(cr);
+            }
         }
         if (pubProviders.size() > 0) {
-            pw.print(prefix); pw.print("pubProviders="); pw.println(pubProviders);
+            pw.print(prefix); pw.println("Published Providers:");
+            for (HashMap.Entry<String, ContentProviderRecord> ent : pubProviders.entrySet()) {
+                pw.print(prefix); pw.print("  - "); pw.println(ent.getKey());
+                pw.print(prefix); pw.print("    -> "); pw.println(ent.getValue());
+            }
         }
         if (conProviders.size() > 0) {
-            pw.print(prefix); pw.print("conProviders="); pw.println(conProviders);
+            pw.print(prefix); pw.println("Connected Providers:");
+            for (int i=0; i<conProviders.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(conProviders.get(i).toShortString());
+            }
         }
         if (receivers.size() > 0) {
-            pw.print(prefix); pw.print("receivers="); pw.println(receivers);
+            pw.print(prefix); pw.println("Receivers:");
+            for (ReceiverList rl : receivers) {
+                pw.print(prefix); pw.print("  - "); pw.println(rl);
+            }
         }
     }
     
diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java
index ccc928f..d148ec3 100644
--- a/services/java/com/android/server/am/ProviderMap.java
+++ b/services/java/com/android/server/am/ProviderMap.java
@@ -177,27 +177,9 @@
         while (it.hasNext()) {
             Map.Entry<ComponentName, ContentProviderRecord> e = it.next();
             ContentProviderRecord r = e.getValue();
-            if (dumpAll) {
-                pw.print("  * ");
-                pw.println(r);
-                r.dump(pw, "    ");
-            } else {
-                pw.print("  * ");
-                pw.println(r);
-                if (r.proc != null) {
-                    pw.print("    proc=");
-                    pw.println(r.proc);
-                }
-                if (r.launchingApp != null) {
-                    pw.print("    launchingApp=");
-                    pw.println(r.launchingApp);
-                }
-                if (r.clients.size() > 0 || r.externalProcessNoHandleCount > 0) {
-                    pw.print("    "); pw.print(r.clients.size());
-                            pw.print(" clients, "); pw.print(r.externalProcessNoHandleCount);
-                            pw.println(" external handles");
-                }
-            }
+            pw.print("  * ");
+            pw.println(r);
+            r.dump(pw, "    ", dumpAll);
         }
     }
 
@@ -210,7 +192,7 @@
             pw.print("  ");
             pw.print(e.getKey());
             pw.print(": ");
-            pw.println(r);
+            pw.println(r.toShortString());
         }
     }
 
@@ -221,10 +203,10 @@
                 pw.println(" ");
             pw.println("  Published content providers (by class):");
             dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass);
-            pw.println("");
         }
 
         if (mProvidersByClassPerUser.size() > 1) {
+            pw.println("");
             for (int i = 0; i < mProvidersByClassPerUser.size(); i++) {
                 HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i);
                 pw.println("  User " + mProvidersByClassPerUser.keyAt(i) + ":");
@@ -321,7 +303,7 @@
                     if (r.proc != null) pw.println(r.proc.pid);
                     else pw.println("(not running)");
             if (dumpAll) {
-                r.dump(pw, innerPrefix);
+                r.dump(pw, innerPrefix, true);
             }
         }
         if (r.proc != null && r.proc.thread != null) {
diff --git a/test-runner/src/android/test/mock/MockContentResolver.java b/test-runner/src/android/test/mock/MockContentResolver.java
index 65eb21b..715da0f 100644
--- a/test-runner/src/android/test/mock/MockContentResolver.java
+++ b/test-runner/src/android/test/mock/MockContentResolver.java
@@ -118,6 +118,11 @@
         return releaseProvider(icp);
     }
 
+    /** @hide */
+    @Override
+    public void unstableProviderDied(IContentProvider icp) {
+    }
+
     /**
      * Overrides {@link android.content.ContentResolver#notifyChange(Uri, ContentObserver, boolean)
      * ContentResolver.notifChange(Uri, ContentObserver, boolean)}. All parameters are ignored.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
index fec2c3f..8d259d7 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentResolver.java
@@ -72,6 +72,11 @@
         return releaseProvider(icp);
     }
 
+    /** @hide */
+    @Override
+    public void unstableProviderDied(IContentProvider icp) {
+    }
+
     /**
      * Stub for the layoutlib bridge content resolver.
      */