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);
+ }
}