diff options
| author | Jeff Sharkey <jsharkey@android.com> | 2013-09-30 14:26:27 -0700 |
|---|---|---|
| committer | Jeff Sharkey <jsharkey@android.com> | 2013-10-01 09:50:41 -0700 |
| commit | 7aa7601c09ab5d87cc15a0ed9a8f511d494a4cbc (patch) | |
| tree | a22ca7e432de4309aedf7785babd5e1b73c28b09 /core/java/android | |
| parent | 7eb5ce03d9697caa2e9caf0437036a937d081e90 (diff) | |
| download | frameworks_base-7aa7601c09ab5d87cc15a0ed9a8f511d494a4cbc.zip frameworks_base-7aa7601c09ab5d87cc15a0ed9a8f511d494a4cbc.tar.gz frameworks_base-7aa7601c09ab5d87cc15a0ed9a8f511d494a4cbc.tar.bz2 | |
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
Diffstat (limited to 'core/java/android')
| -rw-r--r-- | core/java/android/app/ActivityManagerNative.java | 21 | ||||
| -rw-r--r-- | core/java/android/app/ActivityThread.java | 13 | ||||
| -rw-r--r-- | core/java/android/app/ContextImpl.java | 5 | ||||
| -rw-r--r-- | core/java/android/app/IActivityManager.java | 2 | ||||
| -rw-r--r-- | core/java/android/content/ContentProviderClient.java | 176 | ||||
| -rw-r--r-- | core/java/android/content/ContentResolver.java | 5 | ||||
| -rw-r--r-- | core/java/android/provider/DocumentsContract.java | 48 |
7 files changed, 201 insertions, 69 deletions
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 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM 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 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return res; } + public void unstableProviderDied(IBinder connection) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2902,6 +2911,18 @@ class ActivityManagerProxy implements IActivityManager 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 @@ public final class ActivityThread { } } + 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 @@ class ContextImpl extends Context { 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 interface IActivityManager extends IInterface { 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 @@ public interface IActivityManager extends IInterface { 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 @@ import java.util.ArrayList; * 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 @@ public class ContentProviderClient { } /** 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } @@ -212,19 +280,22 @@ public class ContentProviderClient { */ 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 class ContentProviderClient { */ 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 class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { 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 @@ public class ContentProviderClient { mContentResolver.unstableProviderDied(mContentProvider); } throw e; + } finally { + afterRemote(); } } @@ -359,7 +441,7 @@ public class ContentProviderClient { } /** {@hide} */ - public static void closeQuietly(ContentProviderClient client) { + public static void releaseQuietly(ContentProviderClient client) { if (client != null) { try { client.release(); @@ -367,4 +449,12 @@ public class ContentProviderClient { } } } + + 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 95fb685..916a6cd 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -287,6 +287,11 @@ public abstract class ContentResolver { /** @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 @@ public final class DocumentsContract { 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 @@ public final class DocumentsContract { } 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 @@ public final class DocumentsContract { 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); + deleteDocument(client, documentUri); + return true; + } catch (Exception e) { + Log.w(TAG, "Failed to delete document", e); + return false; } finally { - ContentProviderClient.closeQuietly(client); + ContentProviderClient.releaseQuietly(client); } } /** {@hide} */ - public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) { + public static void deleteDocument(ContentProviderClient client, Uri documentUri) + throws RemoteException { final Bundle in = new Bundle(); in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); - try { - final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in); - return true; - } catch (Exception e) { - Log.w(TAG, "Failed to delete document", e); - return false; - } + client.call(METHOD_DELETE_DOCUMENT, null, in); } } |
