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 /packages/DocumentsUI/src/com | |
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 'packages/DocumentsUI/src/com')
8 files changed, 143 insertions, 55 deletions
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.AsyncTask; 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 android.widget.Toast; import com.android.documentsui.model.DocumentInfo; -import java.io.FileNotFoundException; - /** * Dialog to create a new directory. */ @@ -88,12 +90,19 @@ public class CreateDirectoryFragment extends DialogFragment { 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.Fragment; 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 class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { @Override public void onLoaderReset(Loader<DirectoryResult> loader) { - mAdapter.swapCursor(null); + mAdapter.swapResult(null, null); } }; @@ -552,9 +553,16 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { 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 @@ public class DirectoryFragment extends Fragment { } } + 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 @@ public class DirectoryFragment extends Fragment { @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 @@ class DirectoryResult implements AutoCloseable { @Override public void close() { IoUtils.closeQuietly(cursor); - ContentProviderClient.closeQuietly(client); + ContentProviderClient.releaseQuietly(client); cursor = null; client = null; } @@ -158,7 +158,9 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { + 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 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> { } 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.app.FragmentManager; 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 @@ public class DocumentsActivity extends Activity { 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 @@ public class DocumentsActivity extends Activity { @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 @@ package com.android.documentsui; 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 @@ public class DocumentsApplication extends Application { 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 @@ package com.android.documentsui; 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 @@ import java.util.concurrent.TimeUnit; 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 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { /** 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 class RecentLoader extends AsyncTaskLoader<DirectoryResult> { 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 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { @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 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { mFirstPassLatch = new CountDownLatch(mTasks.size()); for (RecentTask task : mTasks.values()) { - sExecutor.execute(task); + executor.execute(task); } try { @@ -184,11 +196,14 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { 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 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { } 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 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> { // 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 @@ public class RootsCache { 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 @@ public class RootsCache { 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.net.Uri; 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 class DocumentInfo implements Durable, Parcelable { } 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 @@ public class DocumentInfo implements Durable, Parcelable { throw asFileNotFoundException(t); } finally { IoUtils.closeQuietly(cursor); - ContentProviderClient.closeQuietly(client); + ContentProviderClient.releaseQuietly(client); } } |