Detect wedged ContentProviders, treat as ANR.

All ContentProvider calls are currently blocking, making it hard for
an app to recover when a remote provider is wedged.  This change adds
hidden support to ContentProviderClient to timeout remote calls,
treating them as ANRs.  This behavior is disabled by default.

Update DocumentsUI to use a 20 second timeout whenever interacting
with a storage provider.

Bug: 10993301, 10819461, 10852518
Change-Id: I10fa3c425c6a7225fff9cb7a0a07659028230cd3
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 3b88973..961ee57 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -758,6 +758,14 @@
             return true;
         }
 
+        case APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder b = data.readStrongBinder();
+            appNotRespondingViaProvider(b);
+            reply.writeNoException();
+            return true;
+        }
+
         case REMOVE_CONTENT_PROVIDER_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder b = data.readStrongBinder();
@@ -2891,6 +2899,7 @@
         reply.recycle();
         return res;
     }
+
     public void unstableProviderDied(IBinder connection) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -2902,6 +2911,18 @@
         reply.recycle();
     }
 
+    @Override
+    public void appNotRespondingViaProvider(IBinder connection) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(connection);
+        mRemote.transact(APP_NOT_RESPONDING_VIA_PROVIDER_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();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 6605b5b..02faeac 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4653,6 +4653,19 @@
         }
     }
 
+    final void appNotRespondingViaProvider(IBinder provider) {
+        synchronized (mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(provider);
+            if (prc != null) {
+                try {
+                    ActivityManagerNative.getDefault()
+                            .appNotRespondingViaProvider(prc.holder.connection);
+                } catch (RemoteException e) {
+                }
+            }
+        }
+    }
+
     private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
             ContentProvider localProvider, IActivityManager.ContentProviderHolder holder) {
         final String auths[] = PATTERN_SEMICOLON.split(holder.info.authority);
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 0ba2ac5..300424c 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -2200,5 +2200,10 @@
         public void unstableProviderDied(IContentProvider icp) {
             mMainThread.handleUnstableProviderDied(icp.asBinder(), true);
         }
+
+        @Override
+        public void appNotRespondingViaProvider(IContentProvider icp) {
+            mMainThread.appNotRespondingViaProvider(icp.asBinder());
+        }
     }
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 9a77377..dfea736 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -139,6 +139,7 @@
     public boolean refContentProvider(IBinder connection, int stableDelta, int unstableDelta)
             throws RemoteException;
     public void unstableProviderDied(IBinder connection) throws RemoteException;
+    public void appNotRespondingViaProvider(IBinder connection) throws RemoteException;
     public PendingIntent getRunningServiceControlPanel(ComponentName service)
             throws RemoteException;
     public ComponentName startService(IApplicationThread caller, Intent service,
@@ -691,4 +692,5 @@
     int TAKE_PERSISTABLE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+179;
     int RELEASE_PERSISTABLE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+180;
     int GET_PERSISTED_URI_PERMISSIONS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+181;
+    int APP_NOT_RESPONDING_VIA_PROVIDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+182;
 }
diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java
index 0650798..cefc27f 100644
--- a/core/java/android/content/ContentProviderClient.java
+++ b/core/java/android/content/ContentProviderClient.java
@@ -16,15 +16,20 @@
 
 package android.content;
 
+import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.DeadObjectException;
+import android.os.Handler;
 import android.os.ICancellationSignal;
-import android.os.RemoteException;
+import android.os.Looper;
 import android.os.ParcelFileDescriptor;
-import android.content.res.AssetFileDescriptor;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
 
 import dalvik.system.CloseGuard;
 
@@ -45,26 +50,64 @@
  * until you are finished with the data they have returned.
  */
 public class ContentProviderClient {
-    private final IContentProvider mContentProvider;
+    private static final String TAG = "ContentProviderClient";
+
+    @GuardedBy("ContentProviderClient.class")
+    private static Handler sAnrHandler;
+
     private final ContentResolver mContentResolver;
+    private final IContentProvider mContentProvider;
     private final String mPackageName;
     private final boolean mStable;
-    private boolean mReleased;
 
     private final CloseGuard mGuard = CloseGuard.get();
 
-    /**
-     * @hide
-     */
-    ContentProviderClient(ContentResolver contentResolver,
-            IContentProvider contentProvider, boolean stable) {
-        mContentProvider = contentProvider;
+    private long mAnrTimeout;
+    private NotRespondingRunnable mAnrRunnable;
+
+    private boolean mReleased;
+
+    /** {@hide} */
+    ContentProviderClient(
+            ContentResolver contentResolver, IContentProvider contentProvider, boolean stable) {
         mContentResolver = contentResolver;
+        mContentProvider = contentProvider;
         mPackageName = contentResolver.mPackageName;
         mStable = stable;
+
         mGuard.open("release");
     }
 
+    /** {@hide} */
+    public void setDetectNotResponding(long timeoutMillis) {
+        synchronized (ContentProviderClient.class) {
+            mAnrTimeout = timeoutMillis;
+
+            if (timeoutMillis > 0) {
+                if (mAnrRunnable == null) {
+                    mAnrRunnable = new NotRespondingRunnable();
+                }
+                if (sAnrHandler == null) {
+                    sAnrHandler = new Handler(Looper.getMainLooper(), null, true /* async */);
+                }
+            } else {
+                mAnrRunnable = null;
+            }
+        }
+    }
+
+    private void beforeRemote() {
+        if (mAnrRunnable != null) {
+            sAnrHandler.postDelayed(mAnrRunnable, mAnrTimeout);
+        }
+    }
+
+    private void afterRemote() {
+        if (mAnrRunnable != null) {
+            sAnrHandler.removeCallbacks(mAnrRunnable);
+        }
+    }
+
     /** See {@link ContentProvider#query ContentProvider.query} */
     public Cursor query(Uri url, String[] projection, String selection,
             String[] selectionArgs, String sortOrder) throws RemoteException {
@@ -72,28 +115,31 @@
     }
 
     /** See {@link ContentProvider#query ContentProvider.query} */
-    public Cursor query(Uri url, String[] projection, String selection,
-            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)
-                    throws RemoteException {
-        ICancellationSignal remoteCancellationSignal = null;
-        if (cancellationSignal != null) {
-            cancellationSignal.throwIfCanceled();
-            remoteCancellationSignal = mContentProvider.createCancellationSignal();
-            cancellationSignal.setRemote(remoteCancellationSignal);
-        }
+    public Cursor query(Uri url, String[] projection, String selection, String[] selectionArgs,
+            String sortOrder, CancellationSignal cancellationSignal) throws RemoteException {
+        beforeRemote();
         try {
-            return mContentProvider.query(mPackageName, url, projection, selection,  selectionArgs,
+            ICancellationSignal remoteCancellationSignal = null;
+            if (cancellationSignal != null) {
+                cancellationSignal.throwIfCanceled();
+                remoteCancellationSignal = mContentProvider.createCancellationSignal();
+                cancellationSignal.setRemote(remoteCancellationSignal);
+            }
+            return mContentProvider.query(mPackageName, url, projection, selection, selectionArgs,
                     sortOrder, remoteCancellationSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#getType ContentProvider.getType} */
     public String getType(Uri url) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.getType(url);
         } catch (DeadObjectException e) {
@@ -101,11 +147,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#getStreamTypes ContentProvider.getStreamTypes} */
     public String[] getStreamTypes(Uri url, String mimeTypeFilter) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.getStreamTypes(url, mimeTypeFilter);
         } catch (DeadObjectException e) {
@@ -113,11 +162,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#canonicalize} */
     public final Uri canonicalize(Uri url) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.canonicalize(mPackageName, url);
         } catch (DeadObjectException e) {
@@ -125,11 +177,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#uncanonicalize} */
     public final Uri uncanonicalize(Uri url) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.uncanonicalize(mPackageName, url);
         } catch (DeadObjectException e) {
@@ -137,12 +192,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#insert ContentProvider.insert} */
-    public Uri insert(Uri url, ContentValues initialValues)
-            throws RemoteException {
+    public Uri insert(Uri url, ContentValues initialValues) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.insert(mPackageName, url, initialValues);
         } catch (DeadObjectException e) {
@@ -150,11 +207,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#bulkInsert ContentProvider.bulkInsert} */
     public int bulkInsert(Uri url, ContentValues[] initialValues) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.bulkInsert(mPackageName, url, initialValues);
         } catch (DeadObjectException e) {
@@ -162,12 +222,15 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#delete ContentProvider.delete} */
     public int delete(Uri url, String selection, String[] selectionArgs)
             throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.delete(mPackageName, url, selection, selectionArgs);
         } catch (DeadObjectException e) {
@@ -175,12 +238,15 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#update ContentProvider.update} */
     public int update(Uri url, ContentValues values, String selection,
             String[] selectionArgs) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.update(mPackageName, url, values, selection, selectionArgs);
         } catch (DeadObjectException e) {
@@ -188,6 +254,8 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -212,19 +280,22 @@
      */
     public ParcelFileDescriptor openFile(Uri url, String mode, CancellationSignal signal)
             throws RemoteException, FileNotFoundException {
-        ICancellationSignal remoteSignal = null;
-        if (signal != null) {
-            signal.throwIfCanceled();
-            remoteSignal = mContentProvider.createCancellationSignal();
-            signal.setRemote(remoteSignal);
-        }
+        beforeRemote();
         try {
+            ICancellationSignal remoteSignal = null;
+            if (signal != null) {
+                signal.throwIfCanceled();
+                remoteSignal = mContentProvider.createCancellationSignal();
+                signal.setRemote(remoteSignal);
+            }
             return mContentProvider.openFile(mPackageName, url, mode, remoteSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -249,19 +320,22 @@
      */
     public AssetFileDescriptor openAssetFile(Uri url, String mode, CancellationSignal signal)
             throws RemoteException, FileNotFoundException {
-        ICancellationSignal remoteSignal = null;
-        if (signal != null) {
-            signal.throwIfCanceled();
-            remoteSignal = mContentProvider.createCancellationSignal();
-            signal.setRemote(remoteSignal);
-        }
+        beforeRemote();
         try {
+            ICancellationSignal remoteSignal = null;
+            if (signal != null) {
+                signal.throwIfCanceled();
+                remoteSignal = mContentProvider.createCancellationSignal();
+                signal.setRemote(remoteSignal);
+            }
             return mContentProvider.openAssetFile(mPackageName, url, mode, remoteSignal);
         } catch (DeadObjectException e) {
             if (!mStable) {
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -275,13 +349,14 @@
     public final AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri,
             String mimeType, Bundle opts, CancellationSignal signal)
             throws RemoteException, FileNotFoundException {
-        ICancellationSignal remoteSignal = null;
-        if (signal != null) {
-            signal.throwIfCanceled();
-            remoteSignal = mContentProvider.createCancellationSignal();
-            signal.setRemote(remoteSignal);
-        }
+        beforeRemote();
         try {
+            ICancellationSignal remoteSignal = null;
+            if (signal != null) {
+                signal.throwIfCanceled();
+                remoteSignal = mContentProvider.createCancellationSignal();
+                signal.setRemote(remoteSignal);
+            }
             return mContentProvider.openTypedAssetFile(
                     mPackageName, uri, mimeType, opts, remoteSignal);
         } catch (DeadObjectException e) {
@@ -289,12 +364,15 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#applyBatch ContentProvider.applyBatch} */
     public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
             throws RemoteException, OperationApplicationException {
+        beforeRemote();
         try {
             return mContentProvider.applyBatch(mPackageName, operations);
         } catch (DeadObjectException e) {
@@ -302,12 +380,14 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
     /** See {@link ContentProvider#call(String, String, Bundle)} */
-    public Bundle call(String method, String arg, Bundle extras)
-            throws RemoteException {
+    public Bundle call(String method, String arg, Bundle extras) throws RemoteException {
+        beforeRemote();
         try {
             return mContentProvider.call(mPackageName, method, arg, extras);
         } catch (DeadObjectException e) {
@@ -315,6 +395,8 @@
                 mContentResolver.unstableProviderDied(mContentProvider);
             }
             throw e;
+        } finally {
+            afterRemote();
         }
     }
 
@@ -359,7 +441,7 @@
     }
 
     /** {@hide} */
-    public static void closeQuietly(ContentProviderClient client) {
+    public static void releaseQuietly(ContentProviderClient client) {
         if (client != null) {
             try {
                 client.release();
@@ -367,4 +449,12 @@
             }
         }
     }
+
+    private class NotRespondingRunnable implements Runnable {
+        @Override
+        public void run() {
+            Log.w(TAG, "Detected provider not responding: " + mContentProvider);
+            mContentResolver.appNotRespondingViaProvider(mContentProvider);
+        }
+    }
 }
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 95fb6858..916a6cd 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -287,6 +287,11 @@
     /** @hide */
     public abstract void unstableProviderDied(IContentProvider icp);
 
+    /** @hide */
+    public void appNotRespondingViaProvider(IContentProvider icp) {
+        throw new UnsupportedOperationException("appNotRespondingViaProvider");
+    }
+
     /**
      * Return the MIME type of the given content URL.
      *
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index 85ec803..8bf6e4f 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -635,17 +635,18 @@
                 documentUri.getAuthority());
         try {
             return getDocumentThumbnail(client, documentUri, size, signal);
-        } catch (RemoteException e) {
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
             return null;
         } finally {
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /** {@hide} */
     public static Bitmap getDocumentThumbnail(
             ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal)
-            throws RemoteException {
+            throws RemoteException, IOException {
         final Bundle openOpts = new Bundle();
         openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size);
 
@@ -693,9 +694,6 @@
                 }
                 return BitmapFactory.decodeFileDescriptor(fd, null, opts);
             }
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e);
-            return null;
         } finally {
             IoUtils.closeQuietly(afd);
         }
@@ -717,55 +715,53 @@
                 parentDocumentUri.getAuthority());
         try {
             return createDocument(client, parentDocumentUri, mimeType, displayName);
+        } catch (Exception e) {
+            Log.w(TAG, "Failed to create document", e);
+            return null;
         } finally {
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
     }
 
     /** {@hide} */
     public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri,
-            String mimeType, String displayName) {
+            String mimeType, String displayName) throws RemoteException {
         final Bundle in = new Bundle();
         in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri));
         in.putString(Document.COLUMN_MIME_TYPE, mimeType);
         in.putString(Document.COLUMN_DISPLAY_NAME, displayName);
 
-        try {
-            final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
-            return buildDocumentUri(
-                    parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
-        } catch (Exception e) {
-            Log.w(TAG, "Failed to create document", e);
-            return null;
-        }
+        final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in);
+        return buildDocumentUri(
+                parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID));
     }
 
     /**
      * Delete the given document.
      *
      * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
+     * @return if the document was deleted successfully.
      */
     public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) {
         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
                 documentUri.getAuthority());
         try {
-            return deleteDocument(client, documentUri);
-        } finally {
-            ContentProviderClient.closeQuietly(client);
-        }
-    }
-
-    /** {@hide} */
-    public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) {
-        final Bundle in = new Bundle();
-        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
-
-        try {
-            final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in);
+            deleteDocument(client, documentUri);
             return true;
         } catch (Exception e) {
             Log.w(TAG, "Failed to delete document", e);
             return false;
+        } finally {
+            ContentProviderClient.releaseQuietly(client);
         }
     }
+
+    /** {@hide} */
+    public static void deleteDocument(ContentProviderClient client, Uri documentUri)
+            throws RemoteException {
+        final Bundle in = new Bundle();
+        in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri));
+
+        client.call(METHOD_DELETE_DOCUMENT, null, in);
+    }
 }
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml
index 71a0567..6faf7f8 100644
--- a/packages/DocumentsUI/AndroidManifest.xml
+++ b/packages/DocumentsUI/AndroidManifest.xml
@@ -2,6 +2,7 @@
         package="com.android.documentsui">
 
     <uses-permission android:name="android.permission.MANAGE_DOCUMENTS" />
+    <uses-permission android:name="android.permission.REMOVE_TASKS" />
 
     <application
         android:name=".DocumentsApplication"
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index b7dcb71..92c30ba 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -64,6 +64,8 @@
     <string name="save_error">Failed to save document</string>
     <!-- Toast shown when creating a folder failed with an error [CHAR LIMIT=48] -->
     <string name="create_error">Failed to create folder</string>
+    <!-- Error message shown when querying for a list of documents failed [CHAR LIMIT=48] -->
+    <string name="query_error">Failed to query documents</string>
 
     <!-- Title of storage root location that contains recently modified or used documents [CHAR LIMIT=24] -->
     <string name="root_recent">Recent</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index 48bfaf0..23a3f22 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -16,10 +16,13 @@
 
 package com.android.documentsui;
 
+import static com.android.documentsui.DocumentsActivity.TAG;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.FragmentManager;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -29,6 +32,7 @@
 import android.os.Bundle;
 import android.provider.DocumentsContract;
 import android.provider.DocumentsContract.Document;
+import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.EditText;
@@ -36,8 +40,6 @@
 
 import com.android.documentsui.model.DocumentInfo;
 
-import java.io.FileNotFoundException;
-
 /**
  * Dialog to create a new directory.
  */
@@ -88,12 +90,19 @@
             final ContentResolver resolver = activity.getContentResolver();
 
             final DocumentInfo cwd = activity.getCurrentDirectory();
-            final Uri childUri = DocumentsContract.createDocument(
-                    resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
+
+            ContentProviderClient client = null;
             try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, cwd.derivedUri.getAuthority());
+                final Uri childUri = DocumentsContract.createDocument(
+                        client, cwd.derivedUri, Document.MIME_TYPE_DIR, mDisplayName);
                 return DocumentInfo.fromUri(resolver, childUri);
-            } catch (FileNotFoundException e) {
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to create directory", e);
                 return null;
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
             }
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 1f11aed..6ff47f8 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -31,6 +31,7 @@
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.app.LoaderManager.LoaderCallbacks;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -259,7 +260,7 @@
             public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
                 if (!isAdded()) return;
 
-                mAdapter.swapCursor(result.cursor);
+                mAdapter.swapResult(result.cursor, result.exception);
 
                 // Push latest state up to UI
                 // TODO: if mode change was racing with us, don't overwrite it
@@ -285,7 +286,7 @@
 
             @Override
             public void onLoaderReset(Loader<DirectoryResult> loader) {
-                mAdapter.swapCursor(null);
+                mAdapter.swapResult(null, null);
             }
         };
 
@@ -552,9 +553,16 @@
                 continue;
             }
 
-            if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) {
+            ContentProviderClient client = null;
+            try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, doc.derivedUri.getAuthority());
+                DocumentsContract.deleteDocument(client, doc.derivedUri);
+            } catch (Exception e) {
                 Log.w(TAG, "Failed to delete " + doc);
                 hadTrouble = true;
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
             }
         }
 
@@ -646,7 +654,7 @@
 
         private List<Footer> mFooters = Lists.newArrayList();
 
-        public void swapCursor(Cursor cursor) {
+        public void swapResult(Cursor cursor, Exception e) {
             mCursor = cursor;
             mCursorCount = cursor != null ? cursor.getCount() : 0;
 
@@ -667,6 +675,11 @@
                 }
             }
 
+            if (e != null) {
+                mFooters.add(new MessageFooter(
+                        3, R.drawable.ic_dialog_alert, getString(R.string.query_error)));
+            }
+
             if (isEmpty()) {
                 mEmptyView.setVisibility(View.VISIBLE);
             } else {
@@ -971,19 +984,23 @@
         @Override
         protected Bitmap doInBackground(Uri... params) {
             final Context context = mIconThumb.getContext();
+            final ContentResolver resolver = context.getContentResolver();
 
+            ContentProviderClient client = null;
             Bitmap result = null;
             try {
-                // TODO: switch to using unstable provider
-                result = DocumentsContract.getDocumentThumbnail(
-                        context.getContentResolver(), mUri, mThumbSize, mSignal);
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, mUri.getAuthority());
+                result = DocumentsContract.getDocumentThumbnail(client, mUri, mThumbSize, mSignal);
                 if (result != null) {
                     final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                             context, mThumbSize);
                     thumbs.put(mUri, result);
                 }
             } catch (Exception e) {
-                Log.w(TAG, "Failed to load thumbnail: " + e);
+                Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e);
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
             }
             return result;
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 0b3ecf8..da0f526 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -56,7 +56,7 @@
     @Override
     public void close() {
         IoUtils.closeQuietly(cursor);
-        ContentProviderClient.closeQuietly(client);
+        ContentProviderClient.releaseQuietly(client);
         cursor = null;
         client = null;
     }
@@ -158,7 +158,9 @@
                 + result.mode + ", sortOrder=" + result.sortOrder);
 
         try {
-            result.client = resolver.acquireUnstableContentProviderClient(authority);
+            result.client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                    resolver, authority);
+
             cursor = result.client.query(
                     mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
             cursor.registerContentObserver(mObserver);
@@ -177,7 +179,7 @@
         } catch (Exception e) {
             Log.w(TAG, "Failed to query", e);
             result.exception = e;
-            ContentProviderClient.closeQuietly(result.client);
+            ContentProviderClient.releaseQuietly(result.client);
         } finally {
             synchronized (this) {
                 mSignal = null;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 4caec8f..7a45641 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -35,6 +35,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.ClipData;
 import android.content.ComponentName;
+import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Intent;
@@ -878,6 +879,7 @@
                         mRoot.authority, mRoot.documentId);
                 return DocumentInfo.fromUri(getContentResolver(), uri);
             } catch (FileNotFoundException e) {
+                Log.w(TAG, "Failed to find root", e);
                 return null;
             }
         }
@@ -1035,12 +1037,26 @@
 
         @Override
         protected Uri doInBackground(Void... params) {
+            final ContentResolver resolver = getContentResolver();
             final DocumentInfo cwd = getCurrentDirectory();
-            final Uri childUri = DocumentsContract.createDocument(
-                    getContentResolver(), cwd.derivedUri, mMimeType, mDisplayName);
+
+            ContentProviderClient client = null;
+            Uri childUri = null;
+            try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        resolver, cwd.derivedUri.getAuthority());
+                childUri = DocumentsContract.createDocument(
+                        client, cwd.derivedUri, mMimeType, mDisplayName);
+            } catch (Exception e) {
+                Log.w(TAG, "Failed to create document", e);
+            } finally {
+                ContentProviderClient.releaseQuietly(client);
+            }
+
             if (childUri != null) {
                 saveStackBlocking();
             }
+
             return childUri;
         }
 
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
index 960181a..6b46e3a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java
@@ -19,13 +19,19 @@
 import android.app.ActivityManager;
 import android.app.Application;
 import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Point;
 import android.net.Uri;
+import android.os.RemoteException;
+import android.text.format.DateUtils;
 
 public class DocumentsApplication extends Application {
+    private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
+
     private RootsCache mRoots;
     private Point mThumbnailsSize;
     private ThumbnailCache mThumbnails;
@@ -44,6 +50,17 @@
         return thumbnails;
     }
 
+    public static ContentProviderClient acquireUnstableProviderOrThrow(
+            ContentResolver resolver, String authority) throws RemoteException {
+        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+                authority);
+        if (client == null) {
+            throw new RemoteException("Failed to acquire provider for " + authority);
+        }
+        client.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
+        return client;
+    }
+
     @Override
     public void onCreate() {
         final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 9a4fb7d..47dbcdf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -19,11 +19,12 @@
 import static com.android.documentsui.DocumentsActivity.TAG;
 import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
 
+import android.app.ActivityManager;
 import android.content.AsyncTaskLoader;
 import android.content.ContentProviderClient;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
+import android.database.MatrixCursor;
 import android.database.MergeCursor;
 import android.net.Uri;
 import android.os.Bundle;
@@ -56,9 +57,8 @@
 public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
     private static final boolean LOGD = true;
 
-    // TODO: adjust for svelte devices
-    // TODO: add support for oneway queries to avoid wedging loader
-    private static final int MAX_OUTSTANDING_RECENTS = 2;
+    private static final int MAX_OUTSTANDING_RECENTS = 4;
+    private static final int MAX_OUTSTANDING_RECENTS_SVELTE = 2;
 
     /**
      * Time to wait for first pass to complete before returning partial results.
@@ -74,20 +74,29 @@
     /** MIME types that should always be excluded from recents. */
     private static final String[] RECENT_REJECT_MIMES = new String[] { Document.MIME_TYPE_DIR };
 
-    private static final ExecutorService sExecutor = buildExecutor();
+    private static ExecutorService sExecutor;
 
     /**
      * Create a bounded thread pool for fetching recents; it creates threads as
      * needed (up to maximum) and reclaims them when finished.
      */
-    private static ExecutorService buildExecutor() {
-        // Create a bounded thread pool for fetching recents; it creates
-        // threads as needed (up to maximum) and reclaims them when finished.
-        final ThreadPoolExecutor executor = new ThreadPoolExecutor(
-                MAX_OUTSTANDING_RECENTS, MAX_OUTSTANDING_RECENTS, 10, TimeUnit.SECONDS,
-                new LinkedBlockingQueue<Runnable>());
-        executor.allowCoreThreadTimeOut(true);
-        return executor;
+    private synchronized static ExecutorService getExecutor(Context context) {
+        if (sExecutor == null) {
+            final ActivityManager am = (ActivityManager) context.getSystemService(
+                    Context.ACTIVITY_SERVICE);
+            final int maxOutstanding = am.isLowRamDevice() ? MAX_OUTSTANDING_RECENTS_SVELTE
+                    : MAX_OUTSTANDING_RECENTS;
+
+            // Create a bounded thread pool for fetching recents; it creates
+            // threads as needed (up to maximum) and reclaims them when finished.
+            final ThreadPoolExecutor executor = new ThreadPoolExecutor(
+                    maxOutstanding, maxOutstanding, 10, TimeUnit.SECONDS,
+                    new LinkedBlockingQueue<Runnable>());
+            executor.allowCoreThreadTimeOut(true);
+            sExecutor = executor;
+        }
+
+        return sExecutor;
     }
 
     private final RootsCache mRoots;
@@ -120,25 +129,26 @@
         public void run() {
             if (isCancelled()) return;
 
-            final ContentResolver resolver = getContext().getContentResolver();
-            final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                    authority);
+            ContentProviderClient client = null;
             try {
+                client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                        getContext().getContentResolver(), authority);
+
                 final Uri uri = DocumentsContract.buildRecentDocumentsUri(authority, rootId);
                 final Cursor cursor = client.query(
                         uri, null, null, null, DirectoryLoader.getQuerySortOrder(mSortOrder));
                 mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT);
-                set(mWithRoot);
-
-                mFirstPassLatch.countDown();
-                if (mFirstPassDone) {
-                    onContentChanged();
-                }
-
             } catch (Exception e) {
-                setException(e);
+                Log.w(TAG, "Failed to load " + authority + ", " + rootId, e);
             } finally {
-                ContentProviderClient.closeQuietly(client);
+                ContentProviderClient.releaseQuietly(client);
+            }
+
+            set(mWithRoot);
+
+            mFirstPassLatch.countDown();
+            if (mFirstPassDone) {
+                onContentChanged();
             }
         }
 
@@ -156,6 +166,8 @@
 
     @Override
     public DirectoryResult loadInBackground() {
+        final ExecutorService executor = getExecutor(getContext());
+
         if (mFirstPassLatch == null) {
             // First time through we kick off all the recent tasks, and wait
             // around to see if everyone finishes quickly.
@@ -170,7 +182,7 @@
 
             mFirstPassLatch = new CountDownLatch(mTasks.size());
             for (RecentTask task : mTasks.values()) {
-                sExecutor.execute(task);
+                executor.execute(task);
             }
 
             try {
@@ -184,11 +196,14 @@
         final long rejectBefore = System.currentTimeMillis() - REJECT_OLDER_THAN;
 
         // Collect all finished tasks
+        boolean allDone = true;
         List<Cursor> cursors = Lists.newArrayList();
         for (RecentTask task : mTasks.values()) {
             if (task.isDone()) {
                 try {
                     final Cursor cursor = task.get();
+                    if (cursor == null) continue;
+
                     final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
                             cursor, mState.acceptMimes, RECENT_REJECT_MIMES, rejectBefore) {
                         @Override
@@ -200,14 +215,16 @@
                 } catch (InterruptedException e) {
                     throw new RuntimeException(e);
                 } catch (ExecutionException e) {
-                    Log.w(TAG, "Failed to load " + task.authority + ", " + task.rootId, e);
+                    // We already logged on other side
                 }
+            } else {
+                allDone = false;
             }
         }
 
         if (LOGD) {
             Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
-            Log.d(TAG, sExecutor.toString());
+            Log.d(TAG, executor.toString());
         }
 
         final DirectoryResult result = new DirectoryResult();
@@ -215,11 +232,18 @@
 
         // Hint to UI if we're still loading
         final Bundle extras = new Bundle();
-        if (cursors.size() != mTasks.size()) {
+        if (!allDone) {
             extras.putBoolean(DocumentsContract.EXTRA_LOADING, true);
         }
 
-        final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+        final Cursor merged;
+        if (cursors.size() > 0) {
+            merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+        } else {
+            // Return something when nobody is ready
+            merged = new MatrixCursor(new String[0]);
+        }
+
         final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder) {
             @Override
             public Bundle getExtras() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index e3908e9..bad0a96 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -243,10 +243,11 @@
 
         final List<RootInfo> roots = Lists.newArrayList();
         final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
-        final ContentProviderClient client = resolver
-                .acquireUnstableContentProviderClient(authority);
+
+        ContentProviderClient client = null;
         Cursor cursor = null;
         try {
+            client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
             cursor = client.query(rootsUri, null, null, null, null);
             while (cursor.moveToNext()) {
                 final RootInfo root = RootInfo.fromRootsCursor(authority, cursor);
@@ -256,7 +257,7 @@
             Log.w(TAG, "Failed to load some roots from " + authority + ": " + e);
         } finally {
             IoUtils.closeQuietly(cursor);
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
         return roots;
     }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index 5091a61..91d9124 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -23,9 +23,10 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.provider.DocumentsContract;
-import android.provider.DocumentsProvider;
 import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
 
+import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.RootCursorWrapper;
 
 import libcore.io.IoUtils;
@@ -178,10 +179,11 @@
     }
 
     public void updateFromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException {
-        final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
-                uri.getAuthority());
+        ContentProviderClient client = null;
         Cursor cursor = null;
         try {
+            client = DocumentsApplication.acquireUnstableProviderOrThrow(
+                    resolver, uri.getAuthority());
             cursor = client.query(uri, null, null, null, null);
             if (!cursor.moveToFirst()) {
                 throw new FileNotFoundException("Missing details for " + uri);
@@ -191,7 +193,7 @@
             throw asFileNotFoundException(t);
         } finally {
             IoUtils.closeQuietly(cursor);
-            ContentProviderClient.closeQuietly(client);
+            ContentProviderClient.releaseQuietly(client);
         }
     }
 
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
index 5a15cd2..e9f2c71 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java
@@ -54,11 +54,20 @@
 public class TestDocumentsProvider extends DocumentsProvider {
     private static final String TAG = "TestDocuments";
 
-    private static final boolean LAG_ROOTS = false;
-    private static final boolean CRASH_ROOTS = false;
-    private static final boolean REFRESH_ROOTS = false;
+    private static final boolean ROOTS_WEDGE = false;
+    private static final boolean ROOTS_LAG = false;
+    private static final boolean ROOTS_CRASH = false;
+    private static final boolean ROOTS_REFRESH = false;
 
-    private static final boolean CRASH_DOCUMENT = false;
+    private static final boolean DOCUMENT_CRASH = false;
+
+    private static final boolean RECENT_WEDGE = false;
+
+    private static final boolean CHILD_WEDGE = false;
+    private static final boolean CHILD_CRASH = false;
+
+    private static final boolean THUMB_WEDGE = false;
+    private static final boolean THUMB_CRASH = false;
 
     private static final String MY_ROOT_ID = "myRoot";
     private static final String MY_DOC_ID = "myDoc";
@@ -95,10 +104,11 @@
     public Cursor queryRoots(String[] projection) throws FileNotFoundException {
         Log.d(TAG, "Someone asked for our roots!");
 
-        if (LAG_ROOTS) SystemClock.sleep(3000);
-        if (CRASH_ROOTS) System.exit(12);
+        if (ROOTS_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+        if (ROOTS_LAG) SystemClock.sleep(3000);
+        if (ROOTS_CRASH) System.exit(12);
 
-        if (REFRESH_ROOTS) {
+        if (ROOTS_REFRESH) {
             new AsyncTask<Void, Void, Void>() {
                 @Override
                 protected Void doInBackground(Void... params) {
@@ -126,7 +136,7 @@
     @Override
     public Cursor queryDocument(String documentId, String[] projection)
             throws FileNotFoundException {
-        if (CRASH_DOCUMENT) System.exit(12);
+        if (DOCUMENT_CRASH) System.exit(12);
 
         final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
         includeFile(result, documentId, 0);
@@ -198,6 +208,9 @@
             String parentDocumentId, String[] projection, String sortOrder)
             throws FileNotFoundException {
 
+        if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+        if (CHILD_CRASH) System.exit(12);
+
         final ContentResolver resolver = getContext().getContentResolver();
         final Uri notifyUri = DocumentsContract.buildDocumentUri(
                 "com.example.documents", parentDocumentId);
@@ -257,6 +270,9 @@
     @Override
     public Cursor queryRecentDocuments(String rootId, String[] projection)
             throws FileNotFoundException {
+
+        if (RECENT_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+
         // Pretend to take a super long time to respond
         SystemClock.sleep(3000);
 
@@ -275,6 +291,10 @@
     @Override
     public AssetFileDescriptor openDocumentThumbnail(
             String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
+
+        if (THUMB_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
+        if (THUMB_CRASH) System.exit(12);
+
         final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
         final Canvas canvas = new Canvas(bitmap);
         final Paint paint = new Paint();
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index ddb6d1a..dc915c6 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -7823,6 +7823,31 @@
         }
     }
 
+    @Override
+    public void appNotRespondingViaProvider(IBinder connection) {
+        enforceCallingPermission(
+                android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()");
+
+        final ContentProviderConnection conn = (ContentProviderConnection) connection;
+        if (conn == null) {
+            Slog.w(TAG, "ContentProviderConnection is null");
+            return;
+        }
+
+        final ProcessRecord host = conn.provider.proc;
+        if (host == null) {
+            Slog.w(TAG, "Failed to find hosting ProcessRecord");
+            return;
+        }
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            appNotResponding(host, null, null, false, "ContentProvider not responding");
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
     public static final void installSystemProviders() {
         List<ProviderInfo> providers;
         synchronized (mSelf) {