summaryrefslogtreecommitdiffstats
path: root/packages/DocumentsUI/src/com
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-09-30 14:26:27 -0700
committerJeff Sharkey <jsharkey@android.com>2013-10-01 09:50:41 -0700
commit7aa7601c09ab5d87cc15a0ed9a8f511d494a4cbc (patch)
treea22ca7e432de4309aedf7785babd5e1b73c28b09 /packages/DocumentsUI/src/com
parent7eb5ce03d9697caa2e9caf0437036a937d081e90 (diff)
downloadframeworks_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')
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java19
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java33
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java8
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java20
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java17
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java84
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsCache.java7
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java10
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);
}
}