am ef4c3790: am 27d3c0fe: Merge "Isolate calls to each remote DocumentsProvider." into klp-dev

* commit 'ef4c3790e52beab467359f6b5125b66fbe1993ef':
  Isolate calls to each remote DocumentsProvider.
This commit is contained in:
Jeff Sharkey
2013-10-02 10:11:18 -07:00
committed by Android Git Automerger
8 changed files with 177 additions and 54 deletions

View File

@ -26,6 +26,7 @@ import android.util.TimeUtils;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
/**
* Abstract Loader that provides an {@link AsyncTask} to do the work. See
@ -123,6 +124,8 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
}
}
private final Executor mExecutor;
volatile LoadTask mTask;
volatile LoadTask mCancellingTask;
@ -131,7 +134,13 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
Handler mHandler;
public AsyncTaskLoader(Context context) {
this(context, AsyncTask.THREAD_POOL_EXECUTOR);
}
/** {@hide} */
public AsyncTaskLoader(Context context, Executor executor) {
super(context);
mExecutor = executor;
}
/**
@ -223,7 +232,7 @@ public abstract class AsyncTaskLoader<D> extends Loader<D> {
}
}
if (DEBUG) Slog.v(TAG, "Executing: " + mTask);
mTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
mTask.executeOnExecutor(mExecutor, (Void[]) null);
}
}

View File

@ -69,7 +69,12 @@ public class CreateDirectoryFragment extends DialogFragment {
@Override
public void onClick(DialogInterface dialog, int which) {
final String displayName = text1.getText().toString();
new CreateDirectoryTask(displayName).execute();
final DocumentsActivity activity = (DocumentsActivity) getActivity();
final DocumentInfo cwd = activity.getCurrentDirectory();
new CreateDirectoryTask(displayName).executeOnExecutor(
ProviderExecutor.forAuthority(cwd.authority));
}
});
builder.setNegativeButton(android.R.string.cancel, null);

View File

@ -27,6 +27,7 @@ import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.app.ActivityManager;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
@ -113,6 +114,7 @@ public class DirectoryFragment extends Fragment {
private boolean mHideGridTitles = false;
private boolean mSvelteRecents;
private Point mThumbSize;
private DocumentsAdapter mAdapter;
@ -203,6 +205,19 @@ public class DirectoryFragment extends Fragment {
return view;
}
@Override
public void onDestroyView() {
super.onDestroyView();
// Cancel any outstanding thumbnail requests
final ViewGroup target = (mListView.getAdapter() != null) ? mListView : mGridView;
final int count = target.getChildCount();
for (int i = 0; i < count; i++) {
final View view = target.getChildAt(i);
mRecycleListener.onMovedToScrapHeap(view);
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@ -225,6 +240,10 @@ public class DirectoryFragment extends Fragment {
mHideGridTitles = (doc != null) && doc.isGridTitlesHidden();
}
final ActivityManager am = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
mSvelteRecents = am.isLowRamDevice() && (mType == TYPE_RECENT_OPEN);
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
@Override
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
@ -260,7 +279,7 @@ public class DirectoryFragment extends Fragment {
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
if (!isAdded()) return;
mAdapter.swapResult(result.cursor, result.exception);
mAdapter.swapResult(result);
// Push latest state up to UI
// TODO: if mode change was racing with us, don't overwrite it
@ -286,7 +305,7 @@ public class DirectoryFragment extends Fragment {
@Override
public void onLoaderReset(Loader<DirectoryResult> loader) {
mAdapter.swapResult(null, null);
mAdapter.swapResult(null);
}
};
@ -654,13 +673,13 @@ public class DirectoryFragment extends Fragment {
private List<Footer> mFooters = Lists.newArrayList();
public void swapResult(Cursor cursor, Exception e) {
mCursor = cursor;
mCursorCount = cursor != null ? cursor.getCount() : 0;
public void swapResult(DirectoryResult result) {
mCursor = result != null ? result.cursor : null;
mCursorCount = mCursor != null ? mCursor.getCount() : 0;
mFooters.clear();
final Bundle extras = cursor != null ? cursor.getExtras() : null;
final Bundle extras = mCursor != null ? mCursor.getExtras() : null;
if (extras != null) {
final String info = extras.getString(DocumentsContract.EXTRA_INFO);
if (info != null) {
@ -675,7 +694,7 @@ public class DirectoryFragment extends Fragment {
}
}
if (e != null) {
if (result != null && result.exception != null) {
mFooters.add(new MessageFooter(
3, R.drawable.ic_dialog_alert, getString(R.string.query_error)));
}
@ -776,7 +795,7 @@ public class DirectoryFragment extends Fragment {
final boolean supportsThumbnail = (docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0;
final boolean allowThumbnail = (state.derivedMode == MODE_GRID)
|| MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, docMimeType);
final boolean showThumbnail = supportsThumbnail && allowThumbnail;
final boolean showThumbnail = supportsThumbnail && allowThumbnail && !mSvelteRecents;
boolean cacheHit = false;
if (showThumbnail) {
@ -790,7 +809,7 @@ public class DirectoryFragment extends Fragment {
final ThumbnailAsyncTask task = new ThumbnailAsyncTask(
uri, iconMime, iconThumb, mThumbSize);
iconThumb.setTag(task);
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
task.executeOnExecutor(ProviderExecutor.forAuthority(docAuthority));
}
}
@ -983,6 +1002,8 @@ public class DirectoryFragment extends Fragment {
@Override
protected Bitmap doInBackground(Uri... params) {
if (isCancelled()) return null;
final Context context = mIconThumb.getContext();
final ContentResolver resolver = context.getContentResolver();

View File

@ -79,7 +79,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
public DirectoryLoader(Context context, int type, RootInfo root, DocumentInfo doc, Uri uri,
int userSortOrder) {
super(context);
super(context, ProviderExecutor.forAuthority(root.authority));
mType = type;
mRoot = root;
mDoc = doc;
@ -157,11 +157,11 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + mUserSortOrder + " --> mode="
+ result.mode + ", sortOrder=" + result.sortOrder);
ContentProviderClient client = null;
try {
result.client = DocumentsApplication.acquireUnstableProviderOrThrow(
resolver, authority);
client = DocumentsApplication.acquireUnstableProviderOrThrow(resolver, authority);
cursor = result.client.query(
cursor = client.query(
mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
cursor.registerContentObserver(mObserver);
@ -175,11 +175,12 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
cursor = new SortingCursorWrapper(cursor, result.sortOrder);
}
result.client = client;
result.cursor = cursor;
} catch (Exception e) {
Log.w(TAG, "Failed to query", e);
result.exception = e;
ContentProviderClient.releaseQuietly(result.client);
ContentProviderClient.releaseQuietly(client);
} finally {
synchronized (this) {
mSignal = null;

View File

@ -91,6 +91,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.Executor;
public class DocumentsActivity extends Activity {
public static final String TAG = "Documents";
@ -215,7 +216,7 @@ public class DocumentsActivity extends Activity {
if (!mState.restored) {
if (mState.action == ACTION_MANAGE) {
final Uri rootUri = getIntent().getData();
new RestoreRootTask(rootUri).execute();
new RestoreRootTask(rootUri).executeOnExecutor(getCurrentExecutor());
} else {
new RestoreStackTask().execute();
}
@ -782,6 +783,15 @@ public class DocumentsActivity extends Activity {
return mState.stack.peek();
}
public Executor getCurrentExecutor() {
final DocumentInfo cwd = getCurrentDirectory();
if (cwd != null && cwd.authority != null) {
return ProviderExecutor.forAuthority(cwd.authority);
} else {
return AsyncTask.THREAD_POOL_EXECUTOR;
}
}
public State getDisplayState() {
return mState;
}
@ -855,7 +865,7 @@ public class DocumentsActivity extends Activity {
mState.stackTouched = true;
if (!mRoots.isRecentsRoot(root)) {
new PickRootTask(root).execute();
new PickRootTask(root).executeOnExecutor(getCurrentExecutor());
} else {
onCurrentDirectoryChanged(ANIM_SIDE);
}
@ -932,7 +942,7 @@ public class DocumentsActivity extends Activity {
onCurrentDirectoryChanged(ANIM_DOWN);
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
// Explicit file picked, return
new ExistingFinishTask(doc.derivedUri).execute();
new ExistingFinishTask(doc.derivedUri).executeOnExecutor(getCurrentExecutor());
} else if (mState.action == ACTION_CREATE) {
// Replace selected file
SaveFragment.get(fm).setReplaceTarget(doc);
@ -966,16 +976,16 @@ public class DocumentsActivity extends Activity {
for (int i = 0; i < size; i++) {
uris[i] = docs.get(i).derivedUri;
}
new ExistingFinishTask(uris).execute();
new ExistingFinishTask(uris).executeOnExecutor(getCurrentExecutor());
}
}
public void onSaveRequested(DocumentInfo replaceTarget) {
new ExistingFinishTask(replaceTarget.derivedUri).execute();
new ExistingFinishTask(replaceTarget.derivedUri).executeOnExecutor(getCurrentExecutor());
}
public void onSaveRequested(String mimeType, String displayName) {
new CreateFinishTask(mimeType, displayName).execute();
new CreateFinishTask(mimeType, displayName).executeOnExecutor(getCurrentExecutor());
}
private void saveStackBlocking() {

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.google.android.collect.Maps;
import java.util.HashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
public class ProviderExecutor extends Thread implements Executor {
@GuardedBy("sExecutors")
private static HashMap<String, ProviderExecutor> sExecutors = Maps.newHashMap();
public static Executor forAuthority(String authority) {
synchronized (sExecutors) {
ProviderExecutor executor = sExecutors.get(authority);
if (executor == null) {
executor = new ProviderExecutor();
executor.setName("ProviderExecutor: " + authority);
executor.start();
sExecutors.put(authority, executor);
}
return executor;
}
}
private final LinkedBlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<Runnable>();
@Override
public void execute(Runnable command) {
Preconditions.checkNotNull(command);
mQueue.add(command);
}
@Override
public void run() {
while (true) {
try {
final Runnable command = mQueue.take();
command.run();
} catch (InterruptedException e) {
// That was weird; let's go look for more tasks.
}
}
}
}

View File

@ -49,9 +49,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
@ -74,30 +72,7 @@ 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 ExecutorService sExecutor;
/**
* Create a bounded thread pool for fetching recents; it creates threads as
* needed (up to maximum) and reclaims them when finished.
*/
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 Semaphore mQueryPermits;
private final RootsCache mRoots;
private final State mState;
@ -129,6 +104,20 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
public void run() {
if (isCancelled()) return;
try {
mQueryPermits.acquire();
} catch (InterruptedException e) {
return;
}
try {
runInternal();
} finally {
mQueryPermits.release();
}
}
public void runInternal() {
ContentProviderClient client = null;
try {
client = DocumentsApplication.acquireUnstableProviderOrThrow(
@ -138,6 +127,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
final Cursor cursor = client.query(
uri, null, null, null, DirectoryLoader.getQuerySortOrder(mSortOrder));
mWithRoot = new RootCursorWrapper(authority, rootId, cursor, MAX_DOCS_FROM_ROOT);
} catch (Exception e) {
Log.w(TAG, "Failed to load " + authority + ", " + rootId, e);
} finally {
@ -162,12 +152,17 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
super(context);
mRoots = roots;
mState = state;
// Keep clients around on high-RAM devices, since we'd be spinning them
// up moments later to fetch thumbnails anyway.
final ActivityManager am = (ActivityManager) getContext().getSystemService(
Context.ACTIVITY_SERVICE);
mQueryPermits = new Semaphore(
am.isLowRamDevice() ? MAX_OUTSTANDING_RECENTS_SVELTE : MAX_OUTSTANDING_RECENTS);
}
@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.
@ -182,7 +177,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
mFirstPassLatch = new CountDownLatch(mTasks.size());
for (RecentTask task : mTasks.values()) {
executor.execute(task);
ProviderExecutor.forAuthority(task.authority).execute(task);
}
try {
@ -224,7 +219,6 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
if (LOGD) {
Log.d(TAG, "Found " + cursors.size() + " of " + mTasks.size() + " recent queries done");
Log.d(TAG, executor.toString());
}
final DirectoryResult result = new DirectoryResult();

View File

@ -66,6 +66,7 @@ public class TestDocumentsProvider extends DocumentsProvider {
private static final boolean CHILD_WEDGE = false;
private static final boolean CHILD_CRASH = false;
private static final boolean THUMB_HUNDREDS = false;
private static final boolean THUMB_WEDGE = false;
private static final boolean THUMB_CRASH = false;
@ -225,6 +226,12 @@ public class TestDocumentsProvider extends DocumentsProvider {
includeFile(result, "localfile3", 0);
includeFile(result, "localfile4", 0);
if (THUMB_HUNDREDS) {
for (int i = 0; i < 256; i++) {
includeFile(result, "i maded u an picshure", Document.FLAG_SUPPORTS_THUMBNAIL);
}
}
synchronized (this) {
// Try picking up an existing network fetch
CloudTask task = mTask != null ? mTask.get() : null;
@ -292,7 +299,7 @@ public class TestDocumentsProvider extends DocumentsProvider {
public AssetFileDescriptor openDocumentThumbnail(
String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException {
if (THUMB_WEDGE) SystemClock.sleep(Integer.MAX_VALUE);
if (THUMB_WEDGE) wedgeUntilCanceled(signal);
if (THUMB_CRASH) System.exit(12);
final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
@ -332,6 +339,18 @@ public class TestDocumentsProvider extends DocumentsProvider {
return true;
}
private static void wedgeUntilCanceled(CancellationSignal signal) {
if (signal != null) {
while (true) {
signal.throwIfCanceled();
SystemClock.sleep(500);
}
} else {
Log.w(TAG, "WEDGING WITHOUT A CANCELLATIONSIGNAL");
SystemClock.sleep(Integer.MAX_VALUE);
}
}
private static void includeFile(MatrixCursor result, String docId, int flags) {
final RowBuilder row = result.newRow();
row.add(Document.COLUMN_DOCUMENT_ID, docId);