am a9ce049d: Load RootInfo in background, invalidation.
* commit 'a9ce049db87259e302e2368d2a4a1c11a94fd831': Load RootInfo in background, invalidation.
This commit is contained in:
@@ -48,14 +48,6 @@
|
|||||||
android:authorities="com.android.documentsui.recents"
|
android:authorities="com.android.documentsui.recents"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
|
||||||
<receiver android:name=".DocumentChangedReceiver">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.provider.action.DOCUMENT_CHANGED" />
|
|
||||||
<data android:mimeType="vnd.android.cursor.dir/root" />
|
|
||||||
<data android:mimeType="vnd.android.cursor.item/root" />
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<!-- TODO: remove when we have real clients -->
|
<!-- TODO: remove when we have real clients -->
|
||||||
<activity android:name=".TestActivity" android:enabled="false">
|
<activity android:name=".TestActivity" android:enabled="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
|||||||
@@ -249,8 +249,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
context, mType, root, doc, contentsUri, state.userSortOrder);
|
context, mType, root, doc, contentsUri, state.userSortOrder);
|
||||||
case TYPE_RECENT_OPEN:
|
case TYPE_RECENT_OPEN:
|
||||||
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
||||||
final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
|
return new RecentLoader(context, roots, state);
|
||||||
return new RecentLoader(context, matchingRoots, state.acceptMimes);
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException("Unknown type " + mType);
|
throw new IllegalStateException("Unknown type " + mType);
|
||||||
}
|
}
|
||||||
@@ -797,7 +796,9 @@ public class DirectoryFragment extends Fragment {
|
|||||||
|
|
||||||
Drawable iconDrawable = null;
|
Drawable iconDrawable = null;
|
||||||
if (mType == TYPE_RECENT_OPEN) {
|
if (mType == TYPE_RECENT_OPEN) {
|
||||||
final RootInfo root = roots.getRoot(docAuthority, docRootId);
|
// We've already had to enumerate roots before any results can
|
||||||
|
// be shown, so this will never block.
|
||||||
|
final RootInfo root = roots.getRootBlocking(docAuthority, docRootId);
|
||||||
iconDrawable = root.loadIcon(context);
|
iconDrawable = root.loadIcon(context);
|
||||||
|
|
||||||
if (summary != null) {
|
if (summary != null) {
|
||||||
@@ -808,7 +809,7 @@ public class DirectoryFragment extends Fragment {
|
|||||||
summary.setVisibility(View.VISIBLE);
|
summary.setVisibility(View.VISIBLE);
|
||||||
hasLine2 = true;
|
hasLine2 = true;
|
||||||
} else {
|
} else {
|
||||||
if (iconDrawable != null && roots.isIconUnique(root)) {
|
if (iconDrawable != null && roots.isIconUniqueBlocking(root)) {
|
||||||
// No summary needed if icon speaks for itself
|
// No summary needed if icon speaks for itself
|
||||||
summary.setVisibility(View.INVISIBLE);
|
summary.setVisibility(View.INVISIBLE);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 static com.android.documentsui.DocumentsActivity.TAG;
|
|
||||||
|
|
||||||
import android.content.BroadcastReceiver;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles changes which invalidate cached data.
|
|
||||||
*/
|
|
||||||
public class DocumentChangedReceiver extends BroadcastReceiver {
|
|
||||||
@Override
|
|
||||||
public void onReceive(Context context, Intent intent) {
|
|
||||||
Log.d(TAG, "Regenerating roots cache");
|
|
||||||
DocumentsApplication.getRootsCache(context).update();
|
|
||||||
// TODO: invalidate cached data in recents provider
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,6 +46,7 @@ import android.graphics.drawable.ColorDrawable;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.graphics.drawable.InsetDrawable;
|
import android.graphics.drawable.InsetDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
@@ -87,6 +88,7 @@ import java.io.FileNotFoundException;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class DocumentsActivity extends Activity {
|
public class DocumentsActivity extends Activity {
|
||||||
@@ -207,8 +209,17 @@ public class DocumentsActivity extends Activity {
|
|||||||
RootsFragment.show(getFragmentManager(), null);
|
RootsFragment.show(getFragmentManager(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!mState.restored) {
|
||||||
|
if (mState.action == ACTION_MANAGE) {
|
||||||
|
final Uri rootUri = getIntent().getData();
|
||||||
|
new RestoreRootTask(rootUri).execute();
|
||||||
|
} else {
|
||||||
|
new RestoreStackTask().execute();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
onCurrentDirectoryChanged(ANIM_NONE);
|
onCurrentDirectoryChanged(ANIM_NONE);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void buildDefaultState() {
|
private void buildDefaultState() {
|
||||||
mState = new State();
|
mState = new State();
|
||||||
@@ -241,22 +252,41 @@ public class DocumentsActivity extends Activity {
|
|||||||
|
|
||||||
mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
|
mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false);
|
||||||
mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this);
|
mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> {
|
||||||
|
private Uri mRootUri;
|
||||||
|
|
||||||
|
public RestoreRootTask(Uri rootUri) {
|
||||||
|
mRootUri = rootUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RootInfo doInBackground(Void... params) {
|
||||||
|
final String rootId = DocumentsContract.getRootId(mRootUri);
|
||||||
|
return mRoots.getRootOneshot(mRootUri.getAuthority(), rootId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(RootInfo root) {
|
||||||
|
if (isDestroyed()) return;
|
||||||
|
mState.restored = true;
|
||||||
|
|
||||||
if (mState.action == ACTION_MANAGE) {
|
|
||||||
final Uri uri = intent.getData();
|
|
||||||
final String rootId = DocumentsContract.getRootId(uri);
|
|
||||||
final RootInfo root = mRoots.getRoot(uri.getAuthority(), rootId);
|
|
||||||
if (root != null) {
|
if (root != null) {
|
||||||
onRootPicked(root, true);
|
onRootPicked(root, true);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Failed to find root: " + uri);
|
Log.w(TAG, "Failed to find root: " + mRootUri);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
private class RestoreStackTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
private volatile boolean mRestoredStack;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
// Restore last stack for calling package
|
// Restore last stack for calling package
|
||||||
// TODO: move into async loader
|
|
||||||
boolean restoredStack = false;
|
|
||||||
final String packageName = getCallingPackage();
|
final String packageName = getCallingPackage();
|
||||||
final Cursor cursor = getContentResolver()
|
final Cursor cursor = getContentResolver()
|
||||||
.query(RecentsProvider.buildResume(packageName), null, null, null, null);
|
.query(RecentsProvider.buildResume(packageName), null, null, null, null);
|
||||||
@@ -265,7 +295,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
final byte[] rawStack = cursor.getBlob(
|
final byte[] rawStack = cursor.getBlob(
|
||||||
cursor.getColumnIndex(ResumeColumns.STACK));
|
cursor.getColumnIndex(ResumeColumns.STACK));
|
||||||
DurableUtils.readFromArray(rawStack, mState.stack);
|
DurableUtils.readFromArray(rawStack, mState.stack);
|
||||||
restoredStack = true;
|
mRestoredStack = true;
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "Failed to resume", e);
|
Log.w(TAG, "Failed to resume", e);
|
||||||
@@ -275,18 +305,28 @@ public class DocumentsActivity extends Activity {
|
|||||||
|
|
||||||
// If restored root isn't valid, fall back to recents
|
// If restored root isn't valid, fall back to recents
|
||||||
final RootInfo root = getCurrentRoot();
|
final RootInfo root = getCurrentRoot();
|
||||||
final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState);
|
final Collection<RootInfo> matchingRoots = mRoots.getMatchingRootsBlocking(mState);
|
||||||
if (!matchingRoots.contains(root)) {
|
if (!matchingRoots.contains(root)) {
|
||||||
mState.stack.reset();
|
mState.stack.reset();
|
||||||
restoredStack = false;
|
mRestoredStack = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Void result) {
|
||||||
|
if (isDestroyed()) return;
|
||||||
|
mState.restored = true;
|
||||||
|
|
||||||
// Only open drawer when not restoring stack, and when not showing
|
// Only open drawer when not restoring stack, and when not showing
|
||||||
// visual content.
|
// visual content.
|
||||||
if (!restoredStack
|
if (!mRestoredStack
|
||||||
&& !MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
|
&& !MimePredicate.mimeMatches(MimePredicate.VISUAL_MIMES, mState.acceptMimes)) {
|
||||||
setRootsDrawerOpen(true);
|
setRootsDrawerOpen(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCurrentDirectoryChanged(ANIM_NONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -935,6 +975,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
public boolean localOnly = false;
|
public boolean localOnly = false;
|
||||||
public boolean showAdvanced = false;
|
public boolean showAdvanced = false;
|
||||||
public boolean stackTouched = false;
|
public boolean stackTouched = false;
|
||||||
|
public boolean restored = false;
|
||||||
|
|
||||||
/** Current user navigation stack; empty implies recents. */
|
/** Current user navigation stack; empty implies recents. */
|
||||||
public DocumentStack stack = new DocumentStack();
|
public DocumentStack stack = new DocumentStack();
|
||||||
@@ -974,6 +1015,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
out.writeInt(localOnly ? 1 : 0);
|
out.writeInt(localOnly ? 1 : 0);
|
||||||
out.writeInt(showAdvanced ? 1 : 0);
|
out.writeInt(showAdvanced ? 1 : 0);
|
||||||
out.writeInt(stackTouched ? 1 : 0);
|
out.writeInt(stackTouched ? 1 : 0);
|
||||||
|
out.writeInt(restored ? 1 : 0);
|
||||||
DurableUtils.writeToParcel(out, stack);
|
DurableUtils.writeToParcel(out, stack);
|
||||||
out.writeString(currentSearch);
|
out.writeString(currentSearch);
|
||||||
out.writeMap(dirState);
|
out.writeMap(dirState);
|
||||||
@@ -992,6 +1034,7 @@ public class DocumentsActivity extends Activity {
|
|||||||
state.localOnly = in.readInt() != 0;
|
state.localOnly = in.readInt() != 0;
|
||||||
state.showAdvanced = in.readInt() != 0;
|
state.showAdvanced = in.readInt() != 0;
|
||||||
state.stackTouched = in.readInt() != 0;
|
state.stackTouched = in.readInt() != 0;
|
||||||
|
state.restored = in.readInt() != 0;
|
||||||
DurableUtils.readFromParcel(in, state.stack);
|
DurableUtils.readFromParcel(in, state.stack);
|
||||||
state.currentSearch = in.readString();
|
state.currentSearch = in.readString();
|
||||||
in.readMap(state.dirState, null);
|
in.readMap(state.dirState, null);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
|
import android.net.Uri;
|
||||||
|
|
||||||
public class DocumentsApplication extends Application {
|
public class DocumentsApplication extends Application {
|
||||||
private RootsCache mRoots;
|
private RootsCache mRoots;
|
||||||
@@ -49,6 +50,8 @@ public class DocumentsApplication extends Application {
|
|||||||
final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024;
|
final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024;
|
||||||
|
|
||||||
mRoots = new RootsCache(this);
|
mRoots = new RootsCache(this);
|
||||||
|
mRoots.updateAsync();
|
||||||
|
|
||||||
mThumbnails = new ThumbnailCache(memoryClassBytes / 4);
|
mThumbnails = new ThumbnailCache(memoryClassBytes / 4);
|
||||||
|
|
||||||
final IntentFilter packageFilter = new IntentFilter();
|
final IntentFilter packageFilter = new IntentFilter();
|
||||||
@@ -77,8 +80,13 @@ public class DocumentsApplication extends Application {
|
|||||||
private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
|
private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
// TODO: narrow changed/removed to only packages that have backends
|
final Uri data = intent.getData();
|
||||||
mRoots.update();
|
if (data != null) {
|
||||||
|
final String packageName = data.getSchemeSpecificPart();
|
||||||
|
mRoots.updatePackageAsync(packageName);
|
||||||
|
} else {
|
||||||
|
mRoots.updateAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import libcore.io.IoUtils;
|
|||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
@@ -80,8 +81,8 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
|||||||
return executor;
|
return executor;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final List<RootInfo> mRoots;
|
private final RootsCache mRoots;
|
||||||
private final String[] mAcceptMimes;
|
private final State mState;
|
||||||
|
|
||||||
private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
|
private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
|
||||||
|
|
||||||
@@ -138,10 +139,10 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public RecentLoader(Context context, List<RootInfo> roots, String[] acceptMimes) {
|
public RecentLoader(Context context, RootsCache roots, State state) {
|
||||||
super(context);
|
super(context);
|
||||||
mRoots = roots;
|
mRoots = roots;
|
||||||
mAcceptMimes = acceptMimes;
|
mState = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -150,7 +151,8 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
|||||||
// First time through we kick off all the recent tasks, and wait
|
// First time through we kick off all the recent tasks, and wait
|
||||||
// around to see if everyone finishes quickly.
|
// around to see if everyone finishes quickly.
|
||||||
|
|
||||||
for (RootInfo root : mRoots) {
|
final Collection<RootInfo> roots = mRoots.getMatchingRootsBlocking(mState);
|
||||||
|
for (RootInfo root : roots) {
|
||||||
if ((root.flags & Root.FLAG_SUPPORTS_RECENTS) != 0) {
|
if ((root.flags & Root.FLAG_SUPPORTS_RECENTS) != 0) {
|
||||||
final RecentTask task = new RecentTask(root.authority, root.rootId);
|
final RecentTask task = new RecentTask(root.authority, root.rootId);
|
||||||
mTasks.put(root, task);
|
mTasks.put(root, task);
|
||||||
@@ -177,7 +179,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
|
|||||||
try {
|
try {
|
||||||
final Cursor cursor = task.get();
|
final Cursor cursor = task.get();
|
||||||
final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
|
final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
|
||||||
cursor, mAcceptMimes, new String[] { Document.MIME_TYPE_DIR }) {
|
cursor, mState.acceptMimes, new String[] { Document.MIME_TYPE_DIR }) {
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
// Ignored, since we manage cursor lifecycle internally
|
// Ignored, since we manage cursor lifecycle internally
|
||||||
|
|||||||
@@ -176,7 +176,6 @@ public class RecentsCreateFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public View getView(int position, View convertView, ViewGroup parent) {
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
final Context context = parent.getContext();
|
final Context context = parent.getContext();
|
||||||
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
|
||||||
|
|
||||||
if (convertView == null) {
|
if (convertView == null) {
|
||||||
final LayoutInflater inflater = LayoutInflater.from(context);
|
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
|
|||||||
@@ -21,10 +21,15 @@ import static com.android.documentsui.DocumentsActivity.TAG;
|
|||||||
import android.content.ContentProviderClient;
|
import android.content.ContentProviderClient;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ProviderInfo;
|
import android.content.pm.ProviderInfo;
|
||||||
|
import android.database.ContentObserver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.DocumentsContract.Root;
|
import android.provider.DocumentsContract.Root;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -32,56 +37,148 @@ import android.util.Log;
|
|||||||
import com.android.documentsui.DocumentsActivity.State;
|
import com.android.documentsui.DocumentsActivity.State;
|
||||||
import com.android.documentsui.model.RootInfo;
|
import com.android.documentsui.model.RootInfo;
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.Objects;
|
import com.android.internal.util.Objects;
|
||||||
import com.google.android.collect.Lists;
|
import com.google.android.collect.Lists;
|
||||||
|
import com.google.android.collect.Sets;
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.Multimap;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
import libcore.io.IoUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache of known storage backends and their roots.
|
* Cache of known storage backends and their roots.
|
||||||
*/
|
*/
|
||||||
public class RootsCache {
|
public class RootsCache {
|
||||||
|
private static final boolean LOGD = true;
|
||||||
|
|
||||||
// TODO: cache roots in local provider to avoid spinning up backends
|
// TODO: cache roots in local provider to avoid spinning up backends
|
||||||
// TODO: root updates should trigger UI refresh
|
// TODO: root updates should trigger UI refresh
|
||||||
|
|
||||||
private static final boolean RECENTS_ENABLED = true;
|
|
||||||
|
|
||||||
private final Context mContext;
|
private final Context mContext;
|
||||||
|
private final ContentObserver mObserver;
|
||||||
|
|
||||||
public List<RootInfo> mRoots = Lists.newArrayList();
|
private final RootInfo mRecentsRoot = new RootInfo();
|
||||||
|
|
||||||
private RootInfo mRecentsRoot;
|
private final Object mLock = new Object();
|
||||||
|
private final CountDownLatch mFirstLoad = new CountDownLatch(1);
|
||||||
|
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private Multimap<String, RootInfo> mRoots = ArrayListMultimap.create();
|
||||||
|
@GuardedBy("mLock")
|
||||||
|
private HashSet<String> mStoppedAuthorities = Sets.newHashSet();
|
||||||
|
|
||||||
|
@GuardedBy("mObservedAuthorities")
|
||||||
|
private final HashSet<String> mObservedAuthorities = Sets.newHashSet();
|
||||||
|
|
||||||
public RootsCache(Context context) {
|
public RootsCache(Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
update();
|
mObserver = new RootsChangedObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class RootsChangedObserver extends ContentObserver {
|
||||||
|
public RootsChangedObserver() {
|
||||||
|
super(new Handler());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onChange(boolean selfChange, Uri uri) {
|
||||||
|
if (LOGD) Log.d(TAG, "Updating roots due to change at " + uri);
|
||||||
|
updateAuthorityAsync(uri.getAuthority());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gather roots from all known storage providers.
|
* Gather roots from all known storage providers.
|
||||||
*/
|
*/
|
||||||
@GuardedBy("ActivityThread")
|
public void updateAsync() {
|
||||||
public void update() {
|
// Special root for recents
|
||||||
mRoots.clear();
|
mRecentsRoot.rootType = Root.ROOT_TYPE_SHORTCUT;
|
||||||
|
mRecentsRoot.icon = R.drawable.ic_root_recent;
|
||||||
|
mRecentsRoot.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
|
||||||
|
mRecentsRoot.title = mContext.getString(R.string.root_recent);
|
||||||
|
mRecentsRoot.availableBytes = -1;
|
||||||
|
|
||||||
if (RECENTS_ENABLED) {
|
new UpdateTask().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
// Create special root for recents
|
|
||||||
final RootInfo root = new RootInfo();
|
|
||||||
root.rootType = Root.ROOT_TYPE_SHORTCUT;
|
|
||||||
root.icon = R.drawable.ic_root_recent;
|
|
||||||
root.flags = Root.FLAG_LOCAL_ONLY | Root.FLAG_SUPPORTS_CREATE;
|
|
||||||
root.title = mContext.getString(R.string.root_recent);
|
|
||||||
root.availableBytes = -1;
|
|
||||||
|
|
||||||
mRoots.add(root);
|
|
||||||
mRecentsRoot = root;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query for other storage backends
|
/**
|
||||||
|
* Gather roots from storage providers belonging to given package name.
|
||||||
|
*/
|
||||||
|
public void updatePackageAsync(String packageName) {
|
||||||
|
// Need at least first load, since we're going to be using previously
|
||||||
|
// cached values for non-matching packages.
|
||||||
|
waitForFirstLoad();
|
||||||
|
new UpdateTask(packageName).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gather roots from storage providers belonging to given authority.
|
||||||
|
*/
|
||||||
|
public void updateAuthorityAsync(String authority) {
|
||||||
|
final ProviderInfo info = mContext.getPackageManager().resolveContentProvider(authority, 0);
|
||||||
|
if (info != null) {
|
||||||
|
updatePackageAsync(info.packageName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void waitForFirstLoad() {
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
success = mFirstLoad.await(15, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
if (!success) {
|
||||||
|
Log.w(TAG, "Timeout waiting for first update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load roots from authorities that are in stopped state. Normal
|
||||||
|
* {@link UpdateTask} passes ignore stopped applications.
|
||||||
|
*/
|
||||||
|
private void loadStoppedAuthorities() {
|
||||||
|
final ContentResolver resolver = mContext.getContentResolver();
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (String authority : mStoppedAuthorities) {
|
||||||
|
if (LOGD) Log.d(TAG, "Loading stopped authority " + authority);
|
||||||
|
mRoots.putAll(authority, loadRootsForAuthority(resolver, authority));
|
||||||
|
}
|
||||||
|
mStoppedAuthorities.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class UpdateTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
private final String mFilterPackage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all roots.
|
||||||
|
*/
|
||||||
|
public UpdateTask() {
|
||||||
|
this(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only update roots belonging to given package name. Other roots will
|
||||||
|
* be copied from cached {@link #mRoots} values.
|
||||||
|
*/
|
||||||
|
public UpdateTask(String filterPackage) {
|
||||||
|
mFilterPackage = filterPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
final long start = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
|
final Multimap<String, RootInfo> roots = ArrayListMultimap.create();
|
||||||
|
final HashSet<String> stoppedAuthorities = Sets.newHashSet();
|
||||||
|
|
||||||
final ContentResolver resolver = mContext.getContentResolver();
|
final ContentResolver resolver = mContext.getContentResolver();
|
||||||
final PackageManager pm = mContext.getPackageManager();
|
final PackageManager pm = mContext.getPackageManager();
|
||||||
final List<ProviderInfo> providers = pm.queryContentProviders(
|
final List<ProviderInfo> providers = pm.queryContentProviders(
|
||||||
@@ -89,57 +186,118 @@ public class RootsCache {
|
|||||||
for (ProviderInfo info : providers) {
|
for (ProviderInfo info : providers) {
|
||||||
if (info.metaData != null && info.metaData.containsKey(
|
if (info.metaData != null && info.metaData.containsKey(
|
||||||
DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
|
DocumentsContract.META_DATA_DOCUMENT_PROVIDER)) {
|
||||||
|
// Ignore stopped packages for now; we might query them
|
||||||
|
// later during UI interaction.
|
||||||
|
if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
|
||||||
|
if (LOGD) Log.d(TAG, "Ignoring stopped authority " + info.authority);
|
||||||
|
stoppedAuthorities.add(info.authority);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: populate roots on background thread, and cache results
|
// Try using cached roots if filtering
|
||||||
final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority);
|
boolean cacheHit = false;
|
||||||
|
if (mFilterPackage != null && !mFilterPackage.equals(info.packageName)) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
if (roots.putAll(info.authority, mRoots.get(info.authority))) {
|
||||||
|
if (LOGD) Log.d(TAG, "Used cached roots for " + info.authority);
|
||||||
|
cacheHit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache miss, or loading everything
|
||||||
|
if (!cacheHit) {
|
||||||
|
roots.putAll(
|
||||||
|
info.authority, loadRootsForAuthority(resolver, info.authority));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final long delta = SystemClock.elapsedRealtime() - start;
|
||||||
|
Log.d(TAG, "Update found " + roots.size() + " roots in " + delta + "ms");
|
||||||
|
synchronized (mLock) {
|
||||||
|
mStoppedAuthorities = stoppedAuthorities;
|
||||||
|
mRoots = roots;
|
||||||
|
}
|
||||||
|
mFirstLoad.countDown();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bring up requested provider and query for all active roots.
|
||||||
|
*/
|
||||||
|
private Collection<RootInfo> loadRootsForAuthority(ContentResolver resolver, String authority) {
|
||||||
|
if (LOGD) Log.d(TAG, "Loading roots for " + authority);
|
||||||
|
|
||||||
|
synchronized (mObservedAuthorities) {
|
||||||
|
if (mObservedAuthorities.add(authority)) {
|
||||||
|
// Watch for any future updates
|
||||||
|
final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
|
||||||
|
mContext.getContentResolver().registerContentObserver(rootsUri, true, mObserver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<RootInfo> roots = Lists.newArrayList();
|
||||||
|
final Uri rootsUri = DocumentsContract.buildRootsUri(authority);
|
||||||
final ContentProviderClient client = resolver
|
final ContentProviderClient client = resolver
|
||||||
.acquireUnstableContentProviderClient(info.authority);
|
.acquireUnstableContentProviderClient(authority);
|
||||||
Cursor cursor = null;
|
Cursor cursor = null;
|
||||||
try {
|
try {
|
||||||
cursor = client.query(rootsUri, null, null, null, null);
|
cursor = client.query(rootsUri, null, null, null, null);
|
||||||
while (cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor);
|
final RootInfo root = RootInfo.fromRootsCursor(authority, cursor);
|
||||||
mRoots.add(root);
|
roots.add(root);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e);
|
Log.w(TAG, "Failed to load some roots from " + authority + ": " + e);
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(cursor);
|
IoUtils.closeQuietly(cursor);
|
||||||
ContentProviderClient.closeQuietly(client);
|
ContentProviderClient.closeQuietly(client);
|
||||||
}
|
}
|
||||||
|
return roots;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the requested {@link RootInfo}, but only loading the roots for the
|
||||||
|
* requested authority. This is useful when we want to load fast without
|
||||||
|
* waiting for all the other roots to come back.
|
||||||
|
*/
|
||||||
|
public RootInfo getRootOneshot(String authority, String rootId) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
RootInfo root = getRootLocked(authority, rootId);
|
||||||
|
if (root == null) {
|
||||||
|
mRoots.putAll(
|
||||||
|
authority, loadRootsForAuthority(mContext.getContentResolver(), authority));
|
||||||
|
root = getRootLocked(authority, rootId);
|
||||||
|
}
|
||||||
|
return root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.d(TAG, "Update found " + mRoots.size() + " roots");
|
public RootInfo getRootBlocking(String authority, String rootId) {
|
||||||
|
waitForFirstLoad();
|
||||||
|
loadStoppedAuthorities();
|
||||||
|
synchronized (mLock) {
|
||||||
|
return getRootLocked(authority, rootId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
private RootInfo getRootLocked(String authority, String rootId) {
|
||||||
public RootInfo findRoot(Uri uri) {
|
for (RootInfo root : mRoots.get(authority)) {
|
||||||
final String authority = uri.getAuthority();
|
if (Objects.equal(root.rootId, rootId)) {
|
||||||
final String docId = DocumentsContract.getDocumentId(uri);
|
|
||||||
for (RootInfo root : mRoots) {
|
|
||||||
if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) {
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
public boolean isIconUniqueBlocking(RootInfo root) {
|
||||||
public RootInfo getRoot(String authority, String rootId) {
|
waitForFirstLoad();
|
||||||
for (RootInfo root : mRoots) {
|
loadStoppedAuthorities();
|
||||||
if (Objects.equal(root.authority, authority) && Objects.equal(root.rootId, rootId)) {
|
synchronized (mLock) {
|
||||||
return root;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
|
||||||
public boolean isIconUnique(RootInfo root) {
|
|
||||||
final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon;
|
final int rootIcon = root.derivedIcon != 0 ? root.derivedIcon : root.icon;
|
||||||
for (RootInfo test : mRoots) {
|
for (RootInfo test : mRoots.get(root.authority)) {
|
||||||
if (Objects.equal(test.authority, root.authority)) {
|
|
||||||
if (Objects.equal(test.rootId, root.rootId)) {
|
if (Objects.equal(test.rootId, root.rootId)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -148,32 +306,37 @@ public class RootsCache {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
|
||||||
public RootInfo getRecentsRoot() {
|
public RootInfo getRecentsRoot() {
|
||||||
return mRecentsRoot;
|
return mRecentsRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
|
||||||
public boolean isRecentsRoot(RootInfo root) {
|
public boolean isRecentsRoot(RootInfo root) {
|
||||||
return mRecentsRoot == root;
|
return mRecentsRoot == root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
public Collection<RootInfo> getRootsBlocking() {
|
||||||
public List<RootInfo> getRoots() {
|
waitForFirstLoad();
|
||||||
return mRoots;
|
loadStoppedAuthorities();
|
||||||
|
synchronized (mLock) {
|
||||||
|
return mRoots.values();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("ActivityThread")
|
public Collection<RootInfo> getMatchingRootsBlocking(State state) {
|
||||||
public List<RootInfo> getMatchingRoots(State state) {
|
waitForFirstLoad();
|
||||||
return getMatchingRoots(mRoots, state);
|
loadStoppedAuthorities();
|
||||||
|
synchronized (mLock) {
|
||||||
|
return getMatchingRoots(mRoots.values(), state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<RootInfo> getMatchingRoots(List<RootInfo> roots, State state) {
|
@VisibleForTesting
|
||||||
ArrayList<RootInfo> matching = Lists.newArrayList();
|
static List<RootInfo> getMatchingRoots(Collection<RootInfo> roots, State state) {
|
||||||
|
final List<RootInfo> matching = Lists.newArrayList();
|
||||||
for (RootInfo root : roots) {
|
for (RootInfo root : roots) {
|
||||||
final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
|
final boolean supportsCreate = (root.flags & Root.FLAG_SUPPORTS_CREATE) != 0;
|
||||||
final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
|
final boolean advanced = (root.flags & Root.FLAG_ADVANCED) != 0;
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ package com.android.documentsui;
|
|||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
|
import android.app.LoaderManager.LoaderCallbacks;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.Loader;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.ResolveInfo;
|
import android.content.pm.ResolveInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
@@ -44,6 +46,7 @@ import com.android.documentsui.model.DocumentInfo;
|
|||||||
import com.android.documentsui.model.RootInfo;
|
import com.android.documentsui.model.RootInfo;
|
||||||
import com.android.internal.util.Objects;
|
import com.android.internal.util.Objects;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -55,6 +58,8 @@ public class RootsFragment extends Fragment {
|
|||||||
private ListView mList;
|
private ListView mList;
|
||||||
private SectionedRootsAdapter mAdapter;
|
private SectionedRootsAdapter mAdapter;
|
||||||
|
|
||||||
|
private LoaderCallbacks<Collection<RootInfo>> mCallbacks;
|
||||||
|
|
||||||
private static final String EXTRA_INCLUDE_APPS = "includeApps";
|
private static final String EXTRA_INCLUDE_APPS = "includeApps";
|
||||||
|
|
||||||
public static void show(FragmentManager fm, Intent includeApps) {
|
public static void show(FragmentManager fm, Intent includeApps) {
|
||||||
@@ -87,27 +92,51 @@ public class RootsFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
super.onResume();
|
super.onActivityCreated(savedInstanceState);
|
||||||
updateRootsAdapter();
|
|
||||||
|
final Context context = getActivity();
|
||||||
|
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
||||||
|
final State state = ((DocumentsActivity) context).getDisplayState();
|
||||||
|
|
||||||
|
mCallbacks = new LoaderCallbacks<Collection<RootInfo>>() {
|
||||||
|
@Override
|
||||||
|
public Loader<Collection<RootInfo>> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new RootsLoader(context, roots, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRootsAdapter() {
|
@Override
|
||||||
final Context context = getActivity();
|
public void onLoadFinished(
|
||||||
|
Loader<Collection<RootInfo>> loader, Collection<RootInfo> result) {
|
||||||
|
if (!isAdded()) return;
|
||||||
|
|
||||||
final State state = ((DocumentsActivity) context).getDisplayState();
|
|
||||||
state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context);
|
|
||||||
|
|
||||||
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
|
||||||
final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
|
|
||||||
final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
|
final Intent includeApps = getArguments().getParcelable(EXTRA_INCLUDE_APPS);
|
||||||
|
|
||||||
mAdapter = new SectionedRootsAdapter(context, matchingRoots, includeApps);
|
mAdapter = new SectionedRootsAdapter(context, result, includeApps);
|
||||||
mList.setAdapter(mAdapter);
|
mList.setAdapter(mAdapter);
|
||||||
|
|
||||||
onCurrentRootChanged();
|
onCurrentRootChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Collection<RootInfo>> loader) {
|
||||||
|
mAdapter = null;
|
||||||
|
mList.setAdapter(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
final Context context = getActivity();
|
||||||
|
final State state = ((DocumentsActivity) context).getDisplayState();
|
||||||
|
state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context);
|
||||||
|
|
||||||
|
getLoaderManager().restartLoader(2, null, mCallbacks);
|
||||||
|
}
|
||||||
|
|
||||||
public void onCurrentRootChanged() {
|
public void onCurrentRootChanged() {
|
||||||
if (mAdapter == null) return;
|
if (mAdapter == null) return;
|
||||||
|
|
||||||
@@ -229,7 +258,8 @@ public class RootsFragment extends Fragment {
|
|||||||
private final RootsAdapter mDevices;
|
private final RootsAdapter mDevices;
|
||||||
private final AppsAdapter mApps;
|
private final AppsAdapter mApps;
|
||||||
|
|
||||||
public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) {
|
public SectionedRootsAdapter(
|
||||||
|
Context context, Collection<RootInfo> roots, Intent includeApps) {
|
||||||
mServices = new RootsAdapter(context);
|
mServices = new RootsAdapter(context);
|
||||||
mShortcuts = new RootsAdapter(context);
|
mShortcuts = new RootsAdapter(context);
|
||||||
mDevices = new RootsAdapter(context);
|
mDevices = new RootsAdapter(context);
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* 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 android.content.AsyncTaskLoader;
|
||||||
|
import android.content.Context;
|
||||||
|
|
||||||
|
import com.android.documentsui.DocumentsActivity.State;
|
||||||
|
import com.android.documentsui.model.RootInfo;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> {
|
||||||
|
private final RootsCache mRoots;
|
||||||
|
private final State mState;
|
||||||
|
|
||||||
|
private Collection<RootInfo> mResult;
|
||||||
|
|
||||||
|
public RootsLoader(Context context, RootsCache roots, State state) {
|
||||||
|
super(context);
|
||||||
|
mRoots = roots;
|
||||||
|
mState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Collection<RootInfo> loadInBackground() {
|
||||||
|
return mRoots.getMatchingRootsBlocking(mState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deliverResult(Collection<RootInfo> result) {
|
||||||
|
if (isReset()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Collection<RootInfo> oldResult = mResult;
|
||||||
|
mResult = result;
|
||||||
|
|
||||||
|
if (isStarted()) {
|
||||||
|
super.deliverResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStartLoading() {
|
||||||
|
if (mResult != null) {
|
||||||
|
deliverResult(mResult);
|
||||||
|
}
|
||||||
|
if (takeContentChanged() || mResult == null) {
|
||||||
|
forceLoad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStopLoading() {
|
||||||
|
cancelLoad();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onReset() {
|
||||||
|
super.onReset();
|
||||||
|
|
||||||
|
// Ensure the loader is stopped
|
||||||
|
onStopLoading();
|
||||||
|
|
||||||
|
mResult = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,7 +68,6 @@ public class SaveFragment extends Fragment {
|
|||||||
public View onCreateView(
|
public View onCreateView(
|
||||||
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
final Context context = inflater.getContext();
|
final Context context = inflater.getContext();
|
||||||
final RootsCache roots = DocumentsApplication.getRootsCache(context);
|
|
||||||
|
|
||||||
final View view = inflater.inflate(R.layout.fragment_save, container, false);
|
final View view = inflater.inflate(R.layout.fragment_save, container, false);
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ public class RootInfo implements Durable, Parcelable {
|
|||||||
public String mimeTypes;
|
public String mimeTypes;
|
||||||
|
|
||||||
/** Derived fields that aren't persisted */
|
/** Derived fields that aren't persisted */
|
||||||
|
public String derivedPackageName;
|
||||||
public String[] derivedMimeTypes;
|
public String[] derivedMimeTypes;
|
||||||
public int derivedIcon;
|
public int derivedIcon;
|
||||||
|
|
||||||
@@ -75,6 +76,7 @@ public class RootInfo implements Durable, Parcelable {
|
|||||||
availableBytes = -1;
|
availableBytes = -1;
|
||||||
mimeTypes = null;
|
mimeTypes = null;
|
||||||
|
|
||||||
|
derivedPackageName = null;
|
||||||
derivedMimeTypes = null;
|
derivedMimeTypes = null;
|
||||||
derivedIcon = 0;
|
derivedIcon = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,8 @@
|
|||||||
package com.android.externalstorage;
|
package com.android.externalstorage;
|
||||||
|
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.ProviderInfo;
|
||||||
import android.content.res.AssetFileDescriptor;
|
import android.content.res.AssetFileDescriptor;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.database.MatrixCursor;
|
import android.database.MatrixCursor;
|
||||||
@@ -52,7 +54,10 @@ import java.lang.ref.WeakReference;
|
|||||||
public class TestDocumentsProvider extends DocumentsProvider {
|
public class TestDocumentsProvider extends DocumentsProvider {
|
||||||
private static final String TAG = "TestDocuments";
|
private static final String TAG = "TestDocuments";
|
||||||
|
|
||||||
|
private static final boolean LAG_ROOTS = false;
|
||||||
private static final boolean CRASH_ROOTS = false;
|
private static final boolean CRASH_ROOTS = false;
|
||||||
|
private static final boolean REFRESH_ROOTS = false;
|
||||||
|
|
||||||
private static final boolean CRASH_DOCUMENT = false;
|
private static final boolean CRASH_DOCUMENT = false;
|
||||||
|
|
||||||
private static final String MY_ROOT_ID = "myRoot";
|
private static final String MY_ROOT_ID = "myRoot";
|
||||||
@@ -78,17 +83,42 @@ public class TestDocumentsProvider extends DocumentsProvider {
|
|||||||
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
|
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String mAuthority;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attachInfo(Context context, ProviderInfo info) {
|
||||||
|
mAuthority = info.authority;
|
||||||
|
super.attachInfo(context, info);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||||
|
Log.d(TAG, "Someone asked for our roots!");
|
||||||
|
|
||||||
|
if (LAG_ROOTS) SystemClock.sleep(3000);
|
||||||
if (CRASH_ROOTS) System.exit(12);
|
if (CRASH_ROOTS) System.exit(12);
|
||||||
|
|
||||||
|
if (REFRESH_ROOTS) {
|
||||||
|
new AsyncTask<Void, Void, Void>() {
|
||||||
|
@Override
|
||||||
|
protected Void doInBackground(Void... params) {
|
||||||
|
SystemClock.sleep(3000);
|
||||||
|
Log.d(TAG, "Notifying that something changed!!");
|
||||||
|
final Uri uri = DocumentsContract.buildRootsUri(mAuthority);
|
||||||
|
getContext().getContentResolver().notifyChange(uri, null, false);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
}
|
||||||
|
|
||||||
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
|
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
|
||||||
final RowBuilder row = result.newRow();
|
final RowBuilder row = result.newRow();
|
||||||
row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
|
row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID);
|
||||||
row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE);
|
row.add(Root.COLUMN_ROOT_TYPE, Root.ROOT_TYPE_SERVICE);
|
||||||
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS);
|
row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS);
|
||||||
row.add(Root.COLUMN_TITLE, "_Test title which is really long");
|
row.add(Root.COLUMN_TITLE, "_Test title which is really long");
|
||||||
row.add(Root.COLUMN_SUMMARY, "_Summary which is also super long text");
|
row.add(Root.COLUMN_SUMMARY,
|
||||||
|
SystemClock.elapsedRealtime() + " summary which is also super long text");
|
||||||
row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID);
|
row.add(Root.COLUMN_DOCUMENT_ID, MY_DOC_ID);
|
||||||
row.add(Root.COLUMN_AVAILABLE_BYTES, 1024);
|
row.add(Root.COLUMN_AVAILABLE_BYTES, 1024);
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user