summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt1
-rw-r--r--core/java/android/provider/DocumentsContract.java9
-rw-r--r--packages/DocumentsUI/Android.mk2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java87
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java18
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java253
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java132
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsCache.java12
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java16
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java5
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java21
11 files changed, 503 insertions, 53 deletions
diff --git a/api/current.txt b/api/current.txt
index 5bdd4482cc6b..7fbc484a4b4b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20865,6 +20865,7 @@ package android.provider {
field public static final int FLAG_PROVIDES_IMAGES = 32; // 0x20
field public static final int FLAG_PROVIDES_VIDEO = 16; // 0x10
field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1
+ field public static final int FLAG_SUPPORTS_RECENTS = 64; // 0x40
field public static final int ROOT_TYPE_DEVICE = 3; // 0x3
field public static final int ROOT_TYPE_SERVICE = 1; // 0x1
field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index b97b7c069f17..f445fd5481a4 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -411,6 +411,15 @@ public final class DocumentsContract {
* @see Intent#EXTRA_MIME_TYPES
*/
public static final int FLAG_PROVIDES_IMAGES = 1 << 5;
+
+ /**
+ * Flag indicating that this root can report recently modified
+ * documents.
+ *
+ * @see #COLUMN_FLAGS
+ * @see DocumentsContract#buildRecentDocumentsUri(String, String)
+ */
+ public static final int FLAG_SUPPORTS_RECENTS = 1 << 6;
}
/**
diff --git a/packages/DocumentsUI/Android.mk b/packages/DocumentsUI/Android.mk
index 853353d45fff..79009532f315 100644
--- a/packages/DocumentsUI/Android.mk
+++ b/packages/DocumentsUI/Android.mk
@@ -5,7 +5,7 @@ LOCAL_MODULE_TAGS := optional
LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 guava
LOCAL_PACKAGE_NAME := DocumentsUI
LOCAL_CERTIFICATE := platform
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 549e196ea6b8..c24341e4202f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -64,6 +64,7 @@ import android.widget.Toast;
import com.android.documentsui.DocumentsActivity.State;
import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Predicate;
import com.google.android.collect.Lists;
@@ -86,6 +87,7 @@ public class DirectoryFragment extends Fragment {
public static final int TYPE_NORMAL = 1;
public static final int TYPE_SEARCH = 2;
+ public static final int TYPE_RECENT_OPEN = 3;
private int mType = TYPE_NORMAL;
@@ -95,7 +97,10 @@ public class DirectoryFragment extends Fragment {
private LoaderCallbacks<DirectoryResult> mCallbacks;
private static final String EXTRA_TYPE = "type";
- private static final String EXTRA_URI = "uri";
+ private static final String EXTRA_AUTHORITY = "authority";
+ private static final String EXTRA_ROOT_ID = "rootId";
+ private static final String EXTRA_DOC_ID = "docId";
+ private static final String EXTRA_QUERY = "query";
private static AtomicInteger sLoaderId = new AtomicInteger(4000);
@@ -104,24 +109,26 @@ public class DirectoryFragment extends Fragment {
private final int mLoaderId = sLoaderId.incrementAndGet();
public static void showNormal(FragmentManager fm, Uri uri) {
- show(fm, TYPE_NORMAL, uri);
+ show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null);
}
public static void showSearch(FragmentManager fm, Uri uri, String query) {
- final Uri searchUri = DocumentsContract.buildSearchDocumentsUri(
- uri.getAuthority(), DocumentsContract.getDocumentId(uri), query);
- show(fm, TYPE_SEARCH, searchUri);
+ show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri),
+ query);
}
- @Deprecated
public static void showRecentsOpen(FragmentManager fm) {
- // TODO: new recents behavior
+ show(fm, TYPE_RECENT_OPEN, null, null, null, null);
}
- private static void show(FragmentManager fm, int type, Uri uri) {
+ private static void show(FragmentManager fm, int type, String authority, String rootId,
+ String docId, String query) {
final Bundle args = new Bundle();
args.putInt(EXTRA_TYPE, type);
- args.putParcelable(EXTRA_URI, uri);
+ args.putString(EXTRA_AUTHORITY, authority);
+ args.putString(EXTRA_ROOT_ID, rootId);
+ args.putString(EXTRA_DOC_ID, docId);
+ args.putString(EXTRA_QUERY, query);
final DirectoryFragment fragment = new DirectoryFragment();
fragment.setArguments(args);
@@ -160,9 +167,8 @@ public class DirectoryFragment extends Fragment {
super.onActivityCreated(savedInstanceState);
final Context context = getActivity();
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- mAdapter = new DocumentsAdapter(uri.getAuthority());
+ mAdapter = new DocumentsAdapter();
mType = getArguments().getInt(EXTRA_TYPE);
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
@@ -170,15 +176,26 @@ public class DirectoryFragment extends Fragment {
public Loader<DirectoryResult> onCreateLoader(int id, Bundle args) {
final State state = getDisplayState(DirectoryFragment.this);
+ final String authority = getArguments().getString(EXTRA_AUTHORITY);
+ final String rootId = getArguments().getString(EXTRA_ROOT_ID);
+ final String docId = getArguments().getString(EXTRA_DOC_ID);
+ final String query = getArguments().getString(EXTRA_QUERY);
+
Uri contentsUri;
- if (mType == TYPE_NORMAL) {
- contentsUri = DocumentsContract.buildChildDocumentsUri(
- uri.getAuthority(), DocumentsContract.getDocumentId(uri));
- } else {
- contentsUri = uri;
- }
+ switch (mType) {
+ case TYPE_NORMAL:
+ contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId);
+ return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+ case TYPE_SEARCH:
+ contentsUri = DocumentsContract.buildSearchDocumentsUri(
+ authority, docId, query);
+ return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+ case TYPE_RECENT_OPEN:
+ return new RecentLoader(context);
+ default:
+ throw new IllegalStateException("Unknown type " + mType);
- return new DirectoryLoader(context, contentsUri, state.sortOrder);
+ }
}
@Override
@@ -246,8 +263,7 @@ public class DirectoryFragment extends Fragment {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final Cursor cursor = mAdapter.getItem(position);
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
+ final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
if (mFilter.apply(doc)) {
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
}
@@ -285,8 +301,7 @@ public class DirectoryFragment extends Fragment {
for (int i = 0; i < size; i++) {
if (checked.valueAt(i)) {
final Cursor cursor = mAdapter.getItem(checked.keyAt(i));
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(uri, cursor);
+ final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
docs.add(doc);
}
}
@@ -401,14 +416,8 @@ public class DirectoryFragment extends Fragment {
}
private class DocumentsAdapter extends BaseAdapter {
- private final String mAuthority;
-
private Cursor mCursor;
- public DocumentsAdapter(String authority) {
- mAuthority = authority;
- }
-
public void swapCursor(Cursor cursor) {
mCursor = cursor;
@@ -443,6 +452,8 @@ public class DirectoryFragment extends Fragment {
final Cursor cursor = getItem(position);
+ final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
+ final String docRootId = getCursorString(cursor, RootCursorWrapper.COLUMN_ROOT_ID);
final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
final String docDisplayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
@@ -466,7 +477,7 @@ public class DirectoryFragment extends Fragment {
}
if ((docFlags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0) {
- final Uri uri = DocumentsContract.buildDocumentUri(mAuthority, docId);
+ final Uri uri = DocumentsContract.buildDocumentUri(docAuthority, docId);
final Bitmap cachedResult = thumbs.get(uri);
if (cachedResult != null) {
icon.setImageBitmap(cachedResult);
@@ -477,19 +488,27 @@ public class DirectoryFragment extends Fragment {
task.execute(uri);
}
} else if (docIcon != 0) {
- icon.setImageDrawable(DocumentInfo.loadIcon(context, mAuthority, docIcon));
+ icon.setImageDrawable(DocumentInfo.loadIcon(context, docAuthority, docIcon));
} else {
icon.setImageDrawable(RootsCache.resolveDocumentIcon(context, docMimeType));
}
title.setText(docDisplayName);
- icon1.setVisibility(View.GONE);
- if (docSummary != null) {
- summary.setText(docSummary);
+ if (mType == TYPE_RECENT_OPEN) {
+ final RootInfo root = roots.getRoot(docAuthority, docRootId);
+ icon1.setVisibility(View.VISIBLE);
+ icon1.setImageDrawable(root.loadIcon(context));
+ summary.setText(root.getDirectoryString());
summary.setVisibility(View.VISIBLE);
} else {
- summary.setVisibility(View.INVISIBLE);
+ icon1.setVisibility(View.GONE);
+ if (docSummary != null) {
+ summary.setText(docSummary);
+ summary.setVisibility(View.VISIBLE);
+ } else {
+ summary.setVisibility(View.INVISIBLE);
+ }
}
if (summaryGrid != null) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index fa674d5ac092..3f016b50143f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -48,14 +48,16 @@ class DirectoryResult implements AutoCloseable {
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
+ private final String mRootId;
private final Uri mUri;
private final int mSortOrder;
private CancellationSignal mSignal;
private DirectoryResult mResult;
- public DirectoryLoader(Context context, Uri uri, int sortOrder) {
+ public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) {
super(context);
+ mRootId = rootId;
mUri = uri;
mSortOrder = sortOrder;
}
@@ -69,12 +71,16 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
mSignal = new CancellationSignal();
}
final DirectoryResult result = new DirectoryResult();
+ final String authority = mUri.getAuthority();
try {
result.client = getContext()
- .getContentResolver().acquireUnstableContentProviderClient(mUri.getAuthority());
+ .getContentResolver().acquireUnstableContentProviderClient(authority);
final Cursor cursor = result.client.query(
- mUri, null, null, null, getQuerySortOrder(), mSignal);
- result.cursor = new SortingCursorWrapper(cursor, mSortOrder);
+ mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal);
+ final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1);
+ final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder);
+
+ result.cursor = sorted;
result.cursor.registerContentObserver(mObserver);
} catch (Exception e) {
result.exception = e;
@@ -149,8 +155,8 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
getContext().getContentResolver().unregisterContentObserver(mObserver);
}
- private String getQuerySortOrder() {
- switch (mSortOrder) {
+ public static String getQuerySortOrder(int sortOrder) {
+ switch (sortOrder) {
case SORT_ORDER_DISPLAY_NAME:
return Document.COLUMN_DISPLAY_NAME + " ASC";
case SORT_ORDER_LAST_MODIFIED:
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
new file mode 100644
index 000000000000..5f6fd1337598
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -0,0 +1,253 @@
+/*
+ * 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.AsyncTaskLoader;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MergeCursor;
+import android.net.Uri;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Root;
+import android.util.Log;
+
+import com.android.documentsui.DocumentsActivity.State;
+import com.android.documentsui.model.RootInfo;
+import com.google.android.collect.Maps;
+import com.google.common.collect.Lists;
+import com.google.common.util.concurrent.AbstractFuture;
+
+import libcore.io.IoUtils;
+
+import java.io.Closeable;
+import java.io.IOException;
+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.TimeUnit;
+
+public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
+
+ public static final int MAX_OUTSTANDING_RECENTS = 2;
+
+ /**
+ * Time to wait for first pass to complete before returning partial results.
+ */
+ public static final int MAX_FIRST_PASS_WAIT_MILLIS = 500;
+
+ /**
+ * Maximum documents from a single root.
+ */
+ public static final int MAX_DOCS_FROM_ROOT = 24;
+
+ private static final ExecutorService sExecutor = buildExecutor();
+
+ /**
+ * 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 final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
+
+ private final int mSortOrder = State.SORT_ORDER_LAST_MODIFIED;
+
+ private CountDownLatch mFirstPassLatch;
+ private volatile boolean mFirstPassDone;
+
+ private DirectoryResult mResult;
+
+ // TODO: create better transfer of ownership around cursor to ensure its
+ // closed in all edge cases.
+
+ public class RecentTask extends AbstractFuture<Cursor> implements Runnable, Closeable {
+ public final String authority;
+ public final String rootId;
+
+ private Cursor mWithRoot;
+
+ public RecentTask(String authority, String rootId) {
+ this.authority = authority;
+ this.rootId = rootId;
+ }
+
+ @Override
+ public void run() {
+ if (isCancelled()) return;
+
+ final ContentResolver resolver = getContext().getContentResolver();
+ final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
+ authority);
+ try {
+ 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);
+ } finally {
+ ContentProviderClient.closeQuietly(client);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ IoUtils.closeQuietly(mWithRoot);
+ }
+ }
+
+ public RecentLoader(Context context) {
+ super(context);
+ }
+
+ @Override
+ public DirectoryResult loadInBackground() {
+ if (mFirstPassLatch == null) {
+ // First time through we kick off all the recent tasks, and wait
+ // around to see if everyone finishes quickly.
+
+ final RootsCache roots = DocumentsApplication.getRootsCache(getContext());
+ for (RootInfo root : roots.getRoots()) {
+ if ((root.flags & Root.FLAG_SUPPORTS_RECENTS) != 0) {
+ final RecentTask task = new RecentTask(root.authority, root.rootId);
+ mTasks.put(root, task);
+ }
+ }
+
+ mFirstPassLatch = new CountDownLatch(mTasks.size());
+ for (RecentTask task : mTasks.values()) {
+ sExecutor.execute(task);
+ }
+
+ try {
+ mFirstPassLatch.await(MAX_FIRST_PASS_WAIT_MILLIS, TimeUnit.MILLISECONDS);
+ mFirstPassDone = true;
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Collect all finished tasks
+ List<Cursor> cursors = Lists.newArrayList();
+ for (RecentTask task : mTasks.values()) {
+ if (task.isDone()) {
+ try {
+ cursors.add(task.get());
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ } catch (ExecutionException e) {
+ Log.w(TAG, "Failed to load " + task.authority + ", " + task.rootId, e);
+ }
+ }
+ }
+
+ final DirectoryResult result = new DirectoryResult();
+ if (cursors.size() > 0) {
+ final MergeCursor merged = new MergeCursor(cursors.toArray(new Cursor[cursors.size()]));
+ final SortingCursorWrapper sorted = new SortingCursorWrapper(
+ merged, State.SORT_ORDER_LAST_MODIFIED) {
+ @Override
+ public void close() {
+ // Ignored, since we manage cursor lifecycle internally
+ }
+ };
+ result.cursor = sorted;
+ }
+ return result;
+ }
+
+ @Override
+ public void cancelLoadInBackground() {
+ super.cancelLoadInBackground();
+ }
+
+ @Override
+ public void deliverResult(DirectoryResult result) {
+ if (isReset()) {
+ IoUtils.closeQuietly(result);
+ return;
+ }
+ DirectoryResult oldResult = mResult;
+ mResult = result;
+
+ if (isStarted()) {
+ super.deliverResult(result);
+ }
+
+ if (oldResult != null && oldResult != result) {
+ IoUtils.closeQuietly(oldResult);
+ }
+ }
+
+ @Override
+ protected void onStartLoading() {
+ if (mResult != null) {
+ deliverResult(mResult);
+ }
+ if (takeContentChanged() || mResult == null) {
+ forceLoad();
+ }
+ }
+
+ @Override
+ protected void onStopLoading() {
+ cancelLoad();
+ }
+
+ @Override
+ public void onCanceled(DirectoryResult result) {
+ IoUtils.closeQuietly(result);
+ }
+
+ @Override
+ protected void onReset() {
+ super.onReset();
+
+ // Ensure the loader is stopped
+ onStopLoading();
+
+ for (RecentTask task : mTasks.values()) {
+ IoUtils.closeQuietly(task);
+ }
+
+ IoUtils.closeQuietly(mResult);
+ mResult = null;
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java
new file mode 100644
index 000000000000..d0e5ff6312ff
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootCursorWrapper.java
@@ -0,0 +1,132 @@
+/*
+ * 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.database.AbstractCursor;
+import android.database.Cursor;
+
+/**
+ * Cursor wrapper that adds columns to identify which root a document came from.
+ */
+public class RootCursorWrapper extends AbstractCursor {
+ private final String mAuthority;
+ private final String mRootId;
+
+ private final Cursor mCursor;
+ private final int mCount;
+
+ private final String[] mColumnNames;
+
+ private final int mAuthorityIndex;
+ private final int mRootIdIndex;
+
+ public static final String COLUMN_AUTHORITY = "android:authority";
+ public static final String COLUMN_ROOT_ID = "android:rootId";
+
+ public RootCursorWrapper(String authority, String rootId, Cursor cursor, int maxCount) {
+ mAuthority = authority;
+ mRootId = rootId;
+ mCursor = cursor;
+
+ final int count = cursor.getCount();
+ if (maxCount > 0 && count > maxCount) {
+ mCount = maxCount;
+ } else {
+ mCount = count;
+ }
+
+ if (cursor.getColumnIndex(COLUMN_AUTHORITY) != -1
+ || cursor.getColumnIndex(COLUMN_ROOT_ID) != -1) {
+ throw new IllegalArgumentException("Cursor contains internal columns!");
+ }
+ final String[] before = cursor.getColumnNames();
+ mColumnNames = new String[before.length + 2];
+ System.arraycopy(before, 0, mColumnNames, 0, before.length);
+ mAuthorityIndex = before.length;
+ mRootIdIndex = before.length + 1;
+ mColumnNames[mAuthorityIndex] = COLUMN_AUTHORITY;
+ mColumnNames[mRootIdIndex] = COLUMN_ROOT_ID;
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ mCursor.close();
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ return mCursor.moveToPosition(newPosition);
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return mColumnNames;
+ }
+
+ @Override
+ public int getCount() {
+ return mCount;
+ }
+
+ @Override
+ public double getDouble(int column) {
+ return mCursor.getDouble(column);
+ }
+
+ @Override
+ public float getFloat(int column) {
+ return mCursor.getFloat(column);
+ }
+
+ @Override
+ public int getInt(int column) {
+ return mCursor.getInt(column);
+ }
+
+ @Override
+ public long getLong(int column) {
+ return mCursor.getLong(column);
+ }
+
+ @Override
+ public short getShort(int column) {
+ return mCursor.getShort(column);
+ }
+
+ @Override
+ public String getString(int column) {
+ if (column == mAuthorityIndex) {
+ return mAuthority;
+ } else if (column == mRootIdIndex) {
+ return mRootId;
+ } else {
+ return mCursor.getString(column);
+ }
+ }
+
+ @Override
+ public int getType(int column) {
+ return mCursor.getType(column);
+ }
+
+ @Override
+ public boolean isNull(int column) {
+ return mCursor.isNull(column);
+ }
+
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index f67c309a7614..ac3b74089275 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -50,7 +50,7 @@ public class RootsCache {
// TODO: cache roots in local provider to avoid spinning up backends
// TODO: root updates should trigger UI refresh
- private static final boolean RECENTS_ENABLED = false;
+ private static final boolean RECENTS_ENABLED = true;
private final Context mContext;
@@ -126,6 +126,16 @@ public class RootsCache {
}
@GuardedBy("ActivityThread")
+ public RootInfo getRoot(String authority, String rootId) {
+ for (RootInfo root : mRoots) {
+ if (Objects.equal(root.authority, authority) && Objects.equal(root.rootId, rootId)) {
+ return root;
+ }
+ }
+ return null;
+ }
+
+ @GuardedBy("ActivityThread")
public RootInfo getRecentsRoot() {
return mRecentsRoot;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
index 257c106a95c6..b434a35c7f2c 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java
@@ -54,11 +54,6 @@ public class SortingCursorWrapper extends AbstractCursor {
throw new IllegalArgumentException();
}
- final int mimeTypeIndex = cursor.getColumnIndex(Document.COLUMN_MIME_TYPE);
- final int displayNameIndex = cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME);
- final int lastModifiedIndex = cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED);
- final int sizeIndex = cursor.getColumnIndex(Document.COLUMN_SIZE);
-
cursor.moveToPosition(-1);
for (int i = 0; i < count; i++) {
cursor.moveToNext();
@@ -66,8 +61,10 @@ public class SortingCursorWrapper extends AbstractCursor {
switch (sortOrder) {
case SORT_ORDER_DISPLAY_NAME:
- final String mimeType = cursor.getString(mimeTypeIndex);
- final String displayName = cursor.getString(displayNameIndex);
+ final String mimeType = cursor.getString(
+ cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+ final String displayName = cursor.getString(
+ cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME));
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
mValueString[i] = '\001' + displayName;
} else {
@@ -75,10 +72,11 @@ public class SortingCursorWrapper extends AbstractCursor {
}
break;
case SORT_ORDER_LAST_MODIFIED:
- mValueLong[i] = cursor.getLong(lastModifiedIndex);
+ mValueLong[i] = cursor.getLong(
+ cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED));
break;
case SORT_ORDER_SIZE:
- mValueLong[i] = cursor.getLong(sizeIndex);
+ mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_SIZE));
break;
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index feccadc13900..7721bcc7bcdb 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -27,6 +27,7 @@ import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import com.android.documentsui.RecentsProvider;
+import com.android.documentsui.RootCursorWrapper;
import libcore.io.IoUtils;
@@ -101,9 +102,9 @@ public class DocumentInfo implements Durable {
out.writeInt(icon);
}
- public static DocumentInfo fromDirectoryCursor(Uri parent, Cursor cursor) {
+ public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
final DocumentInfo doc = new DocumentInfo();
- final String authority = parent.getAuthority();
+ final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
doc.uri = DocumentsContract.buildDocumentUri(authority, docId);
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index 972883888d4c..189284b2d97e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -25,6 +25,8 @@ import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.provider.DocumentsContract.Root;
+import java.util.Objects;
+
/**
* Representation of a {@link Root}.
*/
@@ -56,4 +58,23 @@ public class RootInfo {
public Drawable loadIcon(Context context) {
return DocumentInfo.loadIcon(context, authority, icon);
}
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof RootInfo) {
+ final RootInfo root = (RootInfo) o;
+ return Objects.equals(authority, root.authority) && Objects.equals(rootId, root.rootId);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(authority, rootId);
+ }
+
+ public String getDirectoryString() {
+ return (summary != null) ? summary : title;
+ }
}