summaryrefslogtreecommitdiffstats
path: root/packages/DocumentsUI
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-09-07 14:45:03 -0700
committerJeff Sharkey <jsharkey@android.com>2013-09-09 09:16:09 -0700
commitd182bb641f228b2d28527a6aa86075f6358ab838 (patch)
tree94eafb66067b4139a014a1f03320c4a9a5f32171 /packages/DocumentsUI
parent0c58bd97384498be14aa9795be9188ca93110e00 (diff)
downloadframeworks_base-d182bb641f228b2d28527a6aa86075f6358ab838.zip
frameworks_base-d182bb641f228b2d28527a6aa86075f6358ab838.tar.gz
frameworks_base-d182bb641f228b2d28527a6aa86075f6358ab838.tar.bz2
Remember mode and sort on per-directory basis.
Persist the last user-selected list/grid mode and sort order for each directory. Remembered user choice always overrides provider hinting. Filter out recent documents that don't match requested MIME type, and show recents in grid mode when picking images. Hide mode and sort order in recents. Add hinting flag for backend to indicate a directory would like to be sorted by last modified. Include explicit root in DocumentStack and clearly mark derived fields. Bug: 10392047, 10608506 Change-Id: I2dd3a0e4112852ebf87e7dbb08b3781c86587dcf
Diffstat (limited to 'packages/DocumentsUI')
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java2
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java105
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java84
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java143
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java119
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java12
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java30
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java15
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java172
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RootsCache.java5
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java117
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java28
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java128
13 files changed, 682 insertions, 278 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
index e0b8d19..d8e60aa 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java
@@ -70,7 +70,7 @@ public class CreateDirectoryFragment extends DialogFragment {
try {
final Uri childUri = DocumentsContract.createDocument(
- resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName);
+ resolver, cwd.derivedUri, Document.MIME_TYPE_DIR, displayName);
// Navigate into newly created child
final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index e594437..a13beba 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -20,6 +20,8 @@ import static com.android.documentsui.DocumentsActivity.TAG;
import static com.android.documentsui.DocumentsActivity.State.ACTION_MANAGE;
import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
+import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import static com.android.documentsui.model.DocumentInfo.getCursorLong;
import static com.android.documentsui.model.DocumentInfo.getCursorString;
@@ -91,43 +93,42 @@ public class DirectoryFragment extends Fragment {
private int mType = TYPE_NORMAL;
+ private int mLastMode = MODE_UNKNOWN;
+ private int mLastSortOrder = SORT_ORDER_UNKNOWN;
+
private Point mThumbSize;
private DocumentsAdapter mAdapter;
private LoaderCallbacks<DirectoryResult> mCallbacks;
private static final String EXTRA_TYPE = "type";
- 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_ROOT = "root";
+ private static final String EXTRA_DOC = "doc";
private static final String EXTRA_QUERY = "query";
private static AtomicInteger sLoaderId = new AtomicInteger(4000);
- private int mLastSortOrder = -1;
-
private final int mLoaderId = sLoaderId.incrementAndGet();
- public static void showNormal(FragmentManager fm, Uri uri) {
- show(fm, TYPE_NORMAL, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri), null);
+ public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc) {
+ show(fm, TYPE_NORMAL, root, doc, null);
}
- public static void showSearch(FragmentManager fm, Uri uri, String query) {
- show(fm, TYPE_SEARCH, uri.getAuthority(), null, DocumentsContract.getDocumentId(uri),
- query);
+ public static void showSearch(
+ FragmentManager fm, RootInfo root, DocumentInfo doc, String query) {
+ show(fm, TYPE_SEARCH, root, doc, query);
}
public static void showRecentsOpen(FragmentManager fm) {
- show(fm, TYPE_RECENT_OPEN, null, null, null, null);
+ show(fm, TYPE_RECENT_OPEN, null, null, null);
}
- private static void show(FragmentManager fm, int type, String authority, String rootId,
- String docId, String query) {
+ private static void show(
+ FragmentManager fm, int type, RootInfo root, DocumentInfo doc, String query) {
final Bundle args = new Bundle();
args.putInt(EXTRA_TYPE, type);
- args.putString(EXTRA_AUTHORITY, authority);
- args.putString(EXTRA_ROOT_ID, rootId);
- args.putString(EXTRA_DOC_ID, docId);
+ args.putParcelable(EXTRA_ROOT, root);
+ args.putParcelable(EXTRA_DOC, doc);
args.putString(EXTRA_QUERY, query);
final DirectoryFragment fragment = new DirectoryFragment();
@@ -167,6 +168,7 @@ public class DirectoryFragment extends Fragment {
super.onActivityCreated(savedInstanceState);
final Context context = getActivity();
+ final State state = getDisplayState(DirectoryFragment.this);
mAdapter = new DocumentsAdapter();
mType = getArguments().getInt(EXTRA_TYPE);
@@ -174,35 +176,48 @@ public class DirectoryFragment extends Fragment {
mCallbacks = new LoaderCallbacks<DirectoryResult>() {
@Override
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 RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
+ final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
final String query = getArguments().getString(EXTRA_QUERY);
Uri contentsUri;
switch (mType) {
case TYPE_NORMAL:
- contentsUri = DocumentsContract.buildChildDocumentsUri(authority, docId);
- return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+ contentsUri = DocumentsContract.buildChildDocumentsUri(
+ doc.authority, doc.documentId);
+ return new DirectoryLoader(context, root, doc, contentsUri);
case TYPE_SEARCH:
contentsUri = DocumentsContract.buildSearchDocumentsUri(
- authority, docId, query);
- return new DirectoryLoader(context, rootId, contentsUri, state.sortOrder);
+ doc.authority, doc.documentId, query);
+ return new DirectoryLoader(context, root, doc, contentsUri);
case TYPE_RECENT_OPEN:
final RootsCache roots = DocumentsApplication.getRootsCache(context);
final List<RootInfo> matchingRoots = roots.getMatchingRoots(state);
- return new RecentLoader(context, matchingRoots);
+ return new RecentLoader(context, matchingRoots, state.acceptMimes);
default:
throw new IllegalStateException("Unknown type " + mType);
-
}
}
@Override
public void onLoadFinished(Loader<DirectoryResult> loader, DirectoryResult result) {
+ if (!isAdded()) return;
+
mAdapter.swapCursor(result.cursor);
+
+ // Push latest state up to UI
+ // TODO: if mode change was racing with us, don't overwrite it
+ state.mode = result.mode;
+ state.sortOrder = result.sortOrder;
+ ((DocumentsActivity) context).onStateChanged();
+
+ updateDisplayState();
+
+ if (mLastSortOrder != result.sortOrder) {
+ mLastSortOrder = result.sortOrder;
+ mListView.smoothScrollToPosition(0);
+ mGridView.smoothScrollToPosition(0);
+ }
}
@Override
@@ -211,6 +226,9 @@ public class DirectoryFragment extends Fragment {
}
};
+ // Kick off loader at least once
+ getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
+
updateDisplayState();
}
@@ -220,22 +238,27 @@ public class DirectoryFragment extends Fragment {
updateDisplayState();
}
- public void updateDisplayState() {
+ public void onUserSortOrderChanged() {
+ // User change always triggers reload
+ getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
+ }
+
+ public void onUserModeChanged() {
+ // Mode change is just display; no need to reload
+ updateDisplayState();
+ }
+
+ private void updateDisplayState() {
final State state = getDisplayState(this);
- if (mLastSortOrder != state.sortOrder) {
- getLoaderManager().restartLoader(mLoaderId, null, mCallbacks);
- mLastSortOrder = state.sortOrder;
- }
+ mFilter = new MimePredicate(state.acceptMimes);
- mListView.smoothScrollToPosition(0);
- mGridView.smoothScrollToPosition(0);
+ if (mLastMode == state.mode) return;
+ mLastMode = state.mode;
mListView.setVisibility(state.mode == MODE_LIST ? View.VISIBLE : View.GONE);
mGridView.setVisibility(state.mode == MODE_GRID ? View.VISIBLE : View.GONE);
- mFilter = new MimePredicate(state.acceptMimes);
-
final int choiceMode;
if (state.allowMultiple) {
choiceMode = ListView.CHOICE_MODE_MULTIPLE_MODAL;
@@ -254,14 +277,14 @@ public class DirectoryFragment extends Fragment {
mGridView.setChoiceMode(choiceMode);
mCurrentView = mGridView;
} else if (state.mode == MODE_LIST) {
- thumbSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size);
+ thumbSize = getResources().getDimensionPixelSize(R.dimen.icon_size);
mGridView.setAdapter(null);
mGridView.setChoiceMode(ListView.CHOICE_MODE_NONE);
mListView.setAdapter(mAdapter);
mListView.setChoiceMode(choiceMode);
mCurrentView = mListView;
} else {
- throw new IllegalStateException();
+ throw new IllegalStateException("Unknown state " + state.mode);
}
mThumbSize = new Point(thumbSize, thumbSize);
@@ -366,7 +389,7 @@ public class DirectoryFragment extends Fragment {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addCategory(Intent.CATEGORY_DEFAULT);
intent.setType(doc.mimeType);
- intent.putExtra(Intent.EXTRA_STREAM, doc.uri);
+ intent.putExtra(Intent.EXTRA_STREAM, doc.derivedUri);
} else if (docs.size() > 1) {
intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
@@ -377,7 +400,7 @@ public class DirectoryFragment extends Fragment {
final ArrayList<Uri> uris = Lists.newArrayList();
for (DocumentInfo doc : docs) {
mimeTypes.add(doc.mimeType);
- uris.add(doc.uri);
+ uris.add(doc.derivedUri);
}
intent.setType(findCommonMimeType(mimeTypes));
@@ -403,7 +426,7 @@ public class DirectoryFragment extends Fragment {
continue;
}
- if (!DocumentsContract.deleteDocument(resolver, doc.uri)) {
+ if (!DocumentsContract.deleteDocument(resolver, doc.derivedUri)) {
Log.w(TAG, "Failed to delete " + doc);
hadTrouble = true;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
index 6ea57d7..72dfa30 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java
@@ -16,18 +16,29 @@
package com.android.documentsui;
+import static com.android.documentsui.DocumentsActivity.TAG;
+import static com.android.documentsui.DocumentsActivity.State.MODE_UNKNOWN;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_UNKNOWN;
+import static com.android.documentsui.model.DocumentInfo.getCursorInt;
import android.content.AsyncTaskLoader;
import android.content.ContentProviderClient;
+import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;
import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+import com.android.documentsui.DocumentsActivity.State;
+import com.android.documentsui.RecentsProvider.StateColumns;
+import com.android.documentsui.model.DocumentInfo;
+import com.android.documentsui.model.RootInfo;
import libcore.io.IoUtils;
@@ -36,6 +47,9 @@ class DirectoryResult implements AutoCloseable {
Cursor cursor;
Exception exception;
+ int mode = MODE_UNKNOWN;
+ int sortOrder = SORT_ORDER_UNKNOWN;
+
@Override
public void close() {
IoUtils.closeQuietly(cursor);
@@ -48,18 +62,18 @@ class DirectoryResult implements AutoCloseable {
public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();
- private final String mRootId;
+ private final RootInfo mRoot;
+ private final DocumentInfo mDoc;
private final Uri mUri;
- private final int mSortOrder;
private CancellationSignal mSignal;
private DirectoryResult mResult;
- public DirectoryLoader(Context context, String rootId, Uri uri, int sortOrder) {
+ public DirectoryLoader(Context context, RootInfo root, DocumentInfo doc, Uri uri) {
super(context);
- mRootId = rootId;
+ mRoot = root;
+ mDoc = doc;
mUri = uri;
- mSortOrder = sortOrder;
}
@Override
@@ -70,20 +84,65 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
}
mSignal = new CancellationSignal();
}
- final DirectoryResult result = new DirectoryResult();
+
+ final ContentResolver resolver = getContext().getContentResolver();
final String authority = mUri.getAuthority();
+
+ final DirectoryResult result = new DirectoryResult();
+
+ int userMode = State.MODE_UNKNOWN;
+ int userSortOrder = State.SORT_ORDER_UNKNOWN;
+
+ // Pick up any custom modes requested by user
+ Cursor cursor = null;
try {
- result.client = getContext()
- .getContentResolver().acquireUnstableContentProviderClient(authority);
- final Cursor cursor = result.client.query(
- mUri, null, null, null, getQuerySortOrder(mSortOrder), mSignal);
+ final Uri stateUri = RecentsProvider.buildState(
+ mRoot.authority, mRoot.rootId, mDoc.documentId);
+ cursor = resolver.query(stateUri, null, null, null, null);
+ if (cursor.moveToFirst()) {
+ userMode = getCursorInt(cursor, StateColumns.MODE);
+ userSortOrder = getCursorInt(cursor, StateColumns.SORT_ORDER);
+ }
+ } finally {
+ IoUtils.closeQuietly(cursor);
+ }
+
+ if (userMode != State.MODE_UNKNOWN) {
+ result.mode = userMode;
+ } else {
+ if ((mDoc.flags & Document.FLAG_DIR_PREFERS_GRID) != 0) {
+ result.mode = State.MODE_GRID;
+ } else {
+ result.mode = State.MODE_LIST;
+ }
+ }
+
+ if (userSortOrder != State.SORT_ORDER_UNKNOWN) {
+ result.sortOrder = userSortOrder;
+ } else {
+ if ((mDoc.flags & Document.FLAG_DIR_PREFERS_LAST_MODIFIED) != 0) {
+ result.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
+ } else {
+ result.sortOrder = State.SORT_ORDER_DISPLAY_NAME;
+ }
+ }
+
+ Log.d(TAG, "userMode=" + userMode + ", userSortOrder=" + userSortOrder + " --> mode="
+ + result.mode + ", sortOrder=" + result.sortOrder);
+
+ try {
+ result.client = resolver.acquireUnstableContentProviderClient(authority);
+ cursor = result.client.query(
+ mUri, null, null, null, getQuerySortOrder(result.sortOrder), mSignal);
cursor.registerContentObserver(mObserver);
- final Cursor withRoot = new RootCursorWrapper(mUri.getAuthority(), mRootId, cursor, -1);
- final Cursor sorted = new SortingCursorWrapper(withRoot, mSortOrder);
+ final Cursor withRoot = new RootCursorWrapper(
+ mUri.getAuthority(), mRoot.rootId, cursor, -1);
+ final Cursor sorted = new SortingCursorWrapper(withRoot, result.sortOrder);
result.cursor = sorted;
} catch (Exception e) {
+ Log.d(TAG, "Failed to query", e);
result.exception = e;
ContentProviderClient.closeQuietly(result.client);
} finally {
@@ -91,6 +150,7 @@ public class DirectoryLoader extends AsyncTaskLoader<DirectoryResult> {
mSignal = null;
}
}
+
return result;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index 38b2ee8..fe39800 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -60,6 +60,9 @@ import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;
import android.widget.Toast;
+import com.android.documentsui.RecentsProvider.RecentColumns;
+import com.android.documentsui.RecentsProvider.ResumeColumns;
+import com.android.documentsui.RecentsProvider.StateColumns;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
@@ -191,7 +194,7 @@ public class DocumentsActivity extends Activity {
try {
if (cursor.moveToFirst()) {
final byte[] rawStack = cursor.getBlob(
- cursor.getColumnIndex(RecentsProvider.COL_PATH));
+ cursor.getColumnIndex(ResumeColumns.STACK));
DurableUtils.readFromArray(rawStack, mState.stack);
}
} catch (IOException e) {
@@ -204,7 +207,7 @@ public class DocumentsActivity extends Activity {
final RootInfo root = getCurrentRoot();
final List<RootInfo> matchingRoots = mRoots.getMatchingRoots(mState);
if (!matchingRoots.contains(root)) {
- mState.stack.clear();
+ mState.stack.reset();
}
// Only open drawer when showing recents
@@ -343,11 +346,16 @@ public class DocumentsActivity extends Activity {
final MenuItem list = menu.findItem(R.id.menu_list);
final MenuItem settings = menu.findItem(R.id.menu_settings);
- grid.setVisible(mState.mode != MODE_GRID);
- list.setVisible(mState.mode != MODE_LIST);
+ if (cwd != null) {
+ sort.setVisible(true);
+ grid.setVisible(mState.mode != MODE_GRID);
+ list.setVisible(mState.mode != MODE_LIST);
+ } else {
+ sort.setVisible(false);
+ grid.setVisible(false);
+ list.setVisible(false);
+ }
- // No sorting in recents
- sort.setVisible(cwd != null);
// Only sort by size when visible
sortSize.setVisible(mState.showSize);
@@ -392,28 +400,19 @@ public class DocumentsActivity extends Activity {
} else if (id == R.id.menu_search) {
return false;
} else if (id == R.id.menu_sort_name) {
- mState.sortOrder = State.SORT_ORDER_DISPLAY_NAME;
- updateDisplayState();
+ setUserSortOrder(State.SORT_ORDER_DISPLAY_NAME);
return true;
} else if (id == R.id.menu_sort_date) {
- mState.sortOrder = State.SORT_ORDER_LAST_MODIFIED;
- updateDisplayState();
+ setUserSortOrder(State.SORT_ORDER_LAST_MODIFIED);
return true;
} else if (id == R.id.menu_sort_size) {
- mState.sortOrder = State.SORT_ORDER_SIZE;
- updateDisplayState();
+ setUserSortOrder(State.SORT_ORDER_SIZE);
return true;
} else if (id == R.id.menu_grid) {
- // TODO: persist explicit user mode for cwd
- mState.mode = MODE_GRID;
- updateDisplayState();
- invalidateOptionsMenu();
+ setUserMode(State.MODE_GRID);
return true;
} else if (id == R.id.menu_list) {
- // TODO: persist explicit user mode for cwd
- mState.mode = MODE_LIST;
- updateDisplayState();
- invalidateOptionsMenu();
+ setUserMode(State.MODE_LIST);
return true;
} else if (id == R.id.menu_settings) {
startActivity(new Intent(this, SettingsActivity.class));
@@ -423,6 +422,51 @@ public class DocumentsActivity extends Activity {
}
}
+ /**
+ * Update UI to reflect internal state changes not from user.
+ */
+ public void onStateChanged() {
+ invalidateOptionsMenu();
+ }
+
+ /**
+ * Set state sort order based on explicit user action.
+ */
+ private void setUserSortOrder(int sortOrder) {
+ final RootInfo root = getCurrentRoot();
+ final DocumentInfo cwd = getCurrentDirectory();
+
+ // TODO: persist async, then trigger rebind
+ final Uri stateUri = RecentsProvider.buildState(
+ root.authority, root.rootId, cwd.documentId);
+ final ContentValues values = new ContentValues();
+ values.put(StateColumns.SORT_ORDER, sortOrder);
+ getContentResolver().insert(stateUri, values);
+
+ DirectoryFragment.get(getFragmentManager()).onUserSortOrderChanged();
+ onStateChanged();
+ }
+
+ /**
+ * Set state mode based on explicit user action.
+ */
+ private void setUserMode(int mode) {
+ final RootInfo root = getCurrentRoot();
+ final DocumentInfo cwd = getCurrentDirectory();
+
+ // TODO: persist async, then trigger rebind
+ final Uri stateUri = RecentsProvider.buildState(
+ root.authority, root.rootId, cwd.documentId);
+ final ContentValues values = new ContentValues();
+ values.put(StateColumns.MODE, mode);
+ getContentResolver().insert(stateUri, values);
+
+ mState.mode = mode;
+
+ DirectoryFragment.get(getFragmentManager()).onUserModeChanged();
+ onStateChanged();
+ }
+
@Override
public void onBackPressed() {
final int size = mState.stack.size();
@@ -528,8 +572,8 @@ public class DocumentsActivity extends Activity {
};
public RootInfo getCurrentRoot() {
- if (mState.stack.size() > 0) {
- return mState.stack.getRoot(mRoots);
+ if (mState.stack.root != null) {
+ return mState.stack.root;
} else {
return mRoots.getRecentsRoot();
}
@@ -545,6 +589,7 @@ public class DocumentsActivity extends Activity {
private void onCurrentDirectoryChanged() {
final FragmentManager fm = getFragmentManager();
+ final RootInfo root = getCurrentRoot();
final DocumentInfo cwd = getCurrentDirectory();
if (cwd == null) {
@@ -557,10 +602,10 @@ public class DocumentsActivity extends Activity {
} else {
if (mState.currentSearch != null) {
// Ongoing search
- DirectoryFragment.showSearch(fm, cwd.uri, mState.currentSearch);
+ DirectoryFragment.showSearch(fm, root, cwd, mState.currentSearch);
} else {
// Normal boring directory
- DirectoryFragment.showNormal(fm, cwd.uri);
+ DirectoryFragment.showNormal(fm, root, cwd);
}
}
@@ -582,11 +627,6 @@ public class DocumentsActivity extends Activity {
dumpStack();
}
- private void updateDisplayState() {
- // TODO: handle multiple directory stacks on tablets
- DirectoryFragment.get(getFragmentManager()).updateDisplayState();
- }
-
public void onStackPicked(DocumentStack stack) {
mState.stack = stack;
onCurrentDirectoryChanged();
@@ -594,6 +634,7 @@ public class DocumentsActivity extends Activity {
public void onRootPicked(RootInfo root, boolean closeDrawer) {
// Clear entire backstack and start in new root
+ mState.stack.root = root;
mState.stack.clear();
if (!mRoots.isRecentsRoot(root)) {
@@ -633,7 +674,7 @@ public class DocumentsActivity extends Activity {
onCurrentDirectoryChanged();
} else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
// Explicit file picked, return
- onFinished(doc.uri);
+ onFinished(doc.derivedUri);
} else if (mState.action == ACTION_CREATE) {
// Replace selected file
SaveFragment.get(fm).setReplaceTarget(doc);
@@ -641,7 +682,7 @@ public class DocumentsActivity extends Activity {
// First try managing the document; we expect manager to filter
// based on authority, so we don't grant.
final Intent manage = new Intent(DocumentsContract.ACTION_MANAGE_DOCUMENT);
- manage.setData(doc.uri);
+ manage.setData(doc.derivedUri);
try {
startActivity(manage);
@@ -649,7 +690,7 @@ public class DocumentsActivity extends Activity {
// Fall back to viewing
final Intent view = new Intent(Intent.ACTION_VIEW);
view.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- view.setData(doc.uri);
+ view.setData(doc.derivedUri);
try {
startActivity(view);
@@ -665,22 +706,21 @@ public class DocumentsActivity extends Activity {
final int size = docs.size();
final Uri[] uris = new Uri[size];
for (int i = 0; i < size; i++) {
- uris[i] = docs.get(i).uri;
+ uris[i] = docs.get(i).derivedUri;
}
onFinished(uris);
}
}
public void onSaveRequested(DocumentInfo replaceTarget) {
- onFinished(replaceTarget.uri);
+ onFinished(replaceTarget.derivedUri);
}
public void onSaveRequested(String mimeType, String displayName) {
final DocumentInfo cwd = getCurrentDirectory();
- final String authority = cwd.uri.getAuthority();
final Uri childUri = DocumentsContract.createDocument(
- getContentResolver(), cwd.uri, mimeType, displayName);
+ getContentResolver(), cwd.derivedUri, mimeType, displayName);
if (childUri != null) {
onFinished(childUri);
} else {
@@ -698,22 +738,14 @@ public class DocumentsActivity extends Activity {
if (mState.action == ACTION_CREATE) {
// Remember stack for last create
values.clear();
- values.put(RecentsProvider.COL_PATH, rawStack);
- resolver.insert(RecentsProvider.buildRecentCreate(), values);
-
- } else if (mState.action == ACTION_OPEN || mState.action == ACTION_GET_CONTENT) {
- // Remember opened items
- for (Uri uri : uris) {
- values.clear();
- values.put(RecentsProvider.COL_URI, uri.toString());
- resolver.insert(RecentsProvider.buildRecentOpen(), values);
- }
+ values.put(RecentColumns.STACK, rawStack);
+ resolver.insert(RecentsProvider.buildRecent(), values);
}
// Remember location for next app launch
final String packageName = getCallingPackage();
values.clear();
- values.put(RecentsProvider.COL_PATH, rawStack);
+ values.put(ResumeColumns.STACK, rawStack);
resolver.insert(RecentsProvider.buildResume(packageName), values);
final Intent intent = new Intent();
@@ -760,12 +792,14 @@ public class DocumentsActivity extends Activity {
public static final int ACTION_GET_CONTENT = 3;
public static final int ACTION_MANAGE = 4;
- public static final int MODE_LIST = 0;
- public static final int MODE_GRID = 1;
+ public static final int MODE_UNKNOWN = 0;
+ public static final int MODE_LIST = 1;
+ public static final int MODE_GRID = 2;
- public static final int SORT_ORDER_DISPLAY_NAME = 0;
- public static final int SORT_ORDER_LAST_MODIFIED = 1;
- public static final int SORT_ORDER_SIZE = 2;
+ public static final int SORT_ORDER_UNKNOWN = 0;
+ public static final int SORT_ORDER_DISPLAY_NAME = 1;
+ public static final int SORT_ORDER_LAST_MODIFIED = 2;
+ public static final int SORT_ORDER_SIZE = 3;
@Override
public int describeContents() {
@@ -811,9 +845,10 @@ public class DocumentsActivity extends Activity {
}
private void dumpStack() {
- Log.d(TAG, "Current stack:");
+ Log.d(TAG, "Current stack: ");
+ Log.d(TAG, " * " + mState.stack.root);
for (DocumentInfo doc : mState.stack) {
- Log.d(TAG, "--> " + doc);
+ Log.d(TAG, " +-- " + doc);
}
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
new file mode 100644
index 0000000..60f0103
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java
@@ -0,0 +1,119 @@
+/*
+ * 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.database.AbstractCursor;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+/**
+ * Cursor wrapper that filters MIME types not matching given list.
+ */
+public class FilteringCursorWrapper extends AbstractCursor {
+ private final Cursor mCursor;
+
+ private final int[] mPosition;
+ private int mCount;
+
+ public FilteringCursorWrapper(Cursor cursor, String[] acceptMimes) {
+ mCursor = cursor;
+
+ final int count = cursor.getCount();
+ mPosition = new int[count];
+
+ cursor.moveToPosition(-1);
+ while (cursor.moveToNext()) {
+ final String mimeType = cursor.getString(
+ cursor.getColumnIndex(Document.COLUMN_MIME_TYPE));
+ if (MimePredicate.mimeMatches(acceptMimes, mimeType)) {
+ mPosition[mCount++] = cursor.getPosition();
+ }
+ }
+
+ Log.d(TAG, "Before filtering " + cursor.getCount() + ", after " + mCount);
+ }
+
+ @Override
+ public Bundle getExtras() {
+ return mCursor.getExtras();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ mCursor.close();
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ return mCursor.moveToPosition(mPosition[newPosition]);
+ }
+
+ @Override
+ public String[] getColumnNames() {
+ return mCursor.getColumnNames();
+ }
+
+ @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) {
+ 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/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
index 85d0988..b55ce82 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java
@@ -49,6 +49,18 @@ public class MimePredicate implements Predicate<DocumentInfo> {
return false;
}
+ public static boolean mimeMatches(String filter, String[] tests) {
+ if (tests == null) {
+ return true;
+ }
+ for (String test : tests) {
+ if (mimeMatches(filter, test)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static boolean mimeMatches(String[] filters, String test) {
if (filters == null) {
return true;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
index 756a297..57442a0 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentLoader.java
@@ -17,6 +17,9 @@
package com.android.documentsui;
import static com.android.documentsui.DocumentsActivity.TAG;
+import static com.android.documentsui.DocumentsActivity.State.MODE_GRID;
+import static com.android.documentsui.DocumentsActivity.State.MODE_LIST;
+import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED;
import android.content.AsyncTaskLoader;
import android.content.ContentProviderClient;
@@ -79,6 +82,7 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
}
private final List<RootInfo> mRoots;
+ private final String[] mAcceptMimes;
private final HashMap<RootInfo, RecentTask> mTasks = Maps.newHashMap();
@@ -135,9 +139,10 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
}
}
- public RecentLoader(Context context, List<RootInfo> roots) {
+ public RecentLoader(Context context, List<RootInfo> roots, String[] acceptMimes) {
super(context);
mRoots = roots;
+ mAcceptMimes = acceptMimes;
}
@Override
@@ -171,7 +176,15 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
for (RecentTask task : mTasks.values()) {
if (task.isDone()) {
try {
- cursors.add(task.get());
+ final Cursor cursor = task.get();
+ final FilteringCursorWrapper filtered = new FilteringCursorWrapper(
+ cursor, mAcceptMimes) {
+ @Override
+ public void close() {
+ // Ignored, since we manage cursor lifecycle internally
+ }
+ };
+ cursors.add(filtered);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
@@ -181,15 +194,14 @@ public class RecentLoader extends AsyncTaskLoader<DirectoryResult> {
}
final DirectoryResult result = new DirectoryResult();
+
+ final boolean acceptImages = MimePredicate.mimeMatches("image/*", mAcceptMimes);
+ result.mode = acceptImages ? MODE_GRID : MODE_LIST;
+ result.sortOrder = SORT_ORDER_LAST_MODIFIED;
+
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
- }
- };
+ final SortingCursorWrapper sorted = new SortingCursorWrapper(merged, result.sortOrder);
result.cursor = sorted;
}
return result;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
index 461c415..9391ca9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java
@@ -41,8 +41,8 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
+import com.android.documentsui.RecentsProvider.RecentColumns;
import com.android.documentsui.model.DocumentStack;
-import com.android.documentsui.model.RootInfo;
import com.google.android.collect.Lists;
import libcore.io.IoUtils;
@@ -128,7 +128,7 @@ public class RecentsCreateFragment extends Fragment {
public static class RecentsCreateLoader extends UriDerivativeLoader<Uri, List<DocumentStack>> {
public RecentsCreateLoader(Context context) {
- super(context, RecentsProvider.buildRecentCreate());
+ super(context, RecentsProvider.buildRecent());
}
@Override
@@ -137,14 +137,14 @@ public class RecentsCreateFragment extends Fragment {
final ContentResolver resolver = getContext().getContentResolver();
final Cursor cursor = resolver.query(
- uri, null, null, null, RecentsProvider.COL_TIMESTAMP + " DESC", signal);
+ uri, null, null, null, RecentColumns.TIMESTAMP + " DESC", signal);
try {
while (cursor != null && cursor.moveToNext()) {
- final byte[] raw = cursor.getBlob(
- cursor.getColumnIndex(RecentsProvider.COL_PATH));
+ final byte[] rawStack = cursor.getBlob(
+ cursor.getColumnIndex(RecentColumns.STACK));
try {
final DocumentStack stack = new DocumentStack();
- stack.read(new DataInputStream(new ByteArrayInputStream(raw)));
+ stack.read(new DataInputStream(new ByteArrayInputStream(rawStack)));
result.add(stack);
} catch (IOException e) {
Log.w(TAG, "Failed to resolve stack: " + e);
@@ -183,8 +183,7 @@ public class RecentsCreateFragment extends Fragment {
final TextView title = (TextView) convertView.findViewById(android.R.id.title);
final DocumentStack stack = getItem(position);
- final RootInfo root = stack.getRoot(roots);
- icon.setImageDrawable(root.loadIcon(context));
+ icon.setImageDrawable(stack.root.loadIcon(context));
final StringBuilder builder = new StringBuilder();
for (int i = stack.size() - 1; i >= 0; i--) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
index 0c87783..df7ed4a 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -25,51 +25,64 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.net.Uri;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Root;
import android.text.format.DateUtils;
import android.util.Log;
public class RecentsProvider extends ContentProvider {
private static final String TAG = "RecentsProvider";
- // TODO: offer view of recents that handles backend root resolution before
- // returning cursor, include extra columns
+ public static final long MAX_HISTORY_IN_MILLIS = DateUtils.DAY_IN_MILLIS * 45;
- public static final String AUTHORITY = "com.android.documentsui.recents";
+ private static final String AUTHORITY = "com.android.documentsui.recents";
private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- private static final int URI_RECENT_OPEN = 1;
- private static final int URI_RECENT_CREATE = 2;
+ private static final int URI_RECENT = 1;
+ private static final int URI_STATE = 2;
private static final int URI_RESUME = 3;
static {
- sMatcher.addURI(AUTHORITY, "recent_open", URI_RECENT_OPEN);
- sMatcher.addURI(AUTHORITY, "recent_create", URI_RECENT_CREATE);
+ sMatcher.addURI(AUTHORITY, "recent", URI_RECENT);
+ // state/authority/rootId/docId
+ sMatcher.addURI(AUTHORITY, "state/*/*/*", URI_STATE);
+ // resume/packageName
sMatcher.addURI(AUTHORITY, "resume/*", URI_RESUME);
}
- private static final String TABLE_RECENT_OPEN = "recent_open";
- private static final String TABLE_RECENT_CREATE = "recent_create";
- private static final String TABLE_RESUME = "resume";
-
- /**
- * String of URIs pointing at a storage backend, stored as a JSON array,
- * starting with root.
- */
- public static final String COL_PATH = "path";
- public static final String COL_URI = "uri";
- public static final String COL_PACKAGE_NAME = "package_name";
- public static final String COL_TIMESTAMP = "timestamp";
-
- @Deprecated
- public static Uri buildRecentOpen() {
- return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(AUTHORITY).appendPath("recent_open").build();
+ public static final String TABLE_RECENT = "recent";
+ public static final String TABLE_STATE = "state";
+ public static final String TABLE_RESUME = "resume";
+
+ public static class RecentColumns {
+ public static final String STACK = "stack";
+ public static final String TIMESTAMP = "timestamp";
+ }
+
+ public static class StateColumns {
+ public static final String AUTHORITY = "authority";
+ public static final String ROOT_ID = Root.COLUMN_ROOT_ID;
+ public static final String DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID;
+ public static final String MODE = "mode";
+ public static final String SORT_ORDER = "sortOrder";
}
- public static Uri buildRecentCreate() {
+ public static class ResumeColumns {
+ public static final String PACKAGE_NAME = "package_name";
+ public static final String STACK = "stack";
+ public static final String TIMESTAMP = "timestamp";
+ }
+
+ public static Uri buildRecent() {
return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
- .authority(AUTHORITY).appendPath("recent_create").build();
+ .authority(AUTHORITY).appendPath("recent").build();
+ }
+
+ public static Uri buildState(String authority, String rootId, String documentId) {
+ return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(AUTHORITY)
+ .appendPath("state").appendPath(authority).appendPath(rootId).appendPath(documentId)
+ .build();
}
public static Uri buildResume(String packageName) {
@@ -83,35 +96,42 @@ public class RecentsProvider extends ContentProvider {
private static final String DB_NAME = "recents.db";
private static final int VERSION_INIT = 1;
+ private static final int VERSION_AS_BLOB = 3;
public DatabaseHelper(Context context) {
- super(context, DB_NAME, null, VERSION_INIT);
+ super(context, DB_NAME, null, VERSION_AS_BLOB);
}
@Override
public void onCreate(SQLiteDatabase db) {
- db.execSQL("CREATE TABLE " + TABLE_RECENT_OPEN + " (" +
- COL_URI + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
- COL_TIMESTAMP + " INTEGER" +
+
+ db.execSQL("CREATE TABLE " + TABLE_RECENT + " (" +
+ RecentColumns.STACK + " BLOB PRIMARY KEY ON CONFLICT REPLACE," +
+ RecentColumns.TIMESTAMP + " INTEGER" +
")");
- db.execSQL("CREATE TABLE " + TABLE_RECENT_CREATE + " (" +
- COL_PATH + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
- COL_TIMESTAMP + " INTEGER" +
+ db.execSQL("CREATE TABLE " + TABLE_STATE + " (" +
+ StateColumns.AUTHORITY + " TEXT," +
+ StateColumns.ROOT_ID + " TEXT," +
+ StateColumns.DOCUMENT_ID + " TEXT," +
+ StateColumns.MODE + " INTEGER," +
+ StateColumns.SORT_ORDER + " INTEGER," +
+ "PRIMARY KEY (" + StateColumns.AUTHORITY + ", " + StateColumns.ROOT_ID + ", "
+ + StateColumns.DOCUMENT_ID + ")" +
")");
db.execSQL("CREATE TABLE " + TABLE_RESUME + " (" +
- COL_PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
- COL_PATH + " TEXT," +
- COL_TIMESTAMP + " INTEGER" +
+ ResumeColumns.PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
+ ResumeColumns.STACK + " BLOB," +
+ ResumeColumns.TIMESTAMP + " INTEGER" +
")");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database; wiping app data");
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT_OPEN);
- db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT_CREATE);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_RECENT);
+ db.execSQL("DROP TABLE IF EXISTS " + TABLE_STATE);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_RESUME);
onCreate(db);
}
@@ -128,22 +148,23 @@ public class RecentsProvider extends ContentProvider {
String sortOrder) {
final SQLiteDatabase db = mHelper.getReadableDatabase();
switch (sMatcher.match(uri)) {
- case URI_RECENT_OPEN: {
- return db.query(TABLE_RECENT_OPEN, projection,
- buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null);
- }
- case URI_RECENT_CREATE: {
- return db.query(TABLE_RECENT_CREATE, projection,
- buildWhereYounger(DateUtils.WEEK_IN_MILLIS), null, null, null, null);
- }
- case URI_RESUME: {
+ case URI_RECENT:
+ return db.query(TABLE_RECENT, projection,
+ RecentColumns.TIMESTAMP + "<" + MAX_HISTORY_IN_MILLIS, null, null, null,
+ null);
+ case URI_STATE:
+ final String authority = uri.getPathSegments().get(1);
+ final String rootId = uri.getPathSegments().get(2);
+ final String documentId = uri.getPathSegments().get(3);
+ return db.query(TABLE_STATE, projection, StateColumns.AUTHORITY + "=? AND "
+ + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?",
+ new String[] { authority, rootId, documentId }, null, null, null);
+ case URI_RESUME:
final String packageName = uri.getPathSegments().get(1);
- return db.query(TABLE_RESUME, projection, COL_PACKAGE_NAME + "=?",
+ return db.query(TABLE_RESUME, projection, ResumeColumns.PACKAGE_NAME + "=?",
new String[] { packageName }, null, null, null);
- }
- default: {
+ default:
throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
}
}
@@ -156,28 +177,37 @@ public class RecentsProvider extends ContentProvider {
public Uri insert(Uri uri, ContentValues values) {
final SQLiteDatabase db = mHelper.getWritableDatabase();
switch (sMatcher.match(uri)) {
- case URI_RECENT_OPEN: {
- values.put(COL_TIMESTAMP, System.currentTimeMillis());
- db.insert(TABLE_RECENT_OPEN, null, values);
- db.delete(TABLE_RECENT_OPEN, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null);
+ case URI_RECENT:
+ values.put(RecentColumns.TIMESTAMP, System.currentTimeMillis());
+ db.insert(TABLE_RECENT, null, values);
+ db.delete(
+ TABLE_RECENT, RecentColumns.TIMESTAMP + ">" + MAX_HISTORY_IN_MILLIS, null);
return uri;
- }
- case URI_RECENT_CREATE: {
- values.put(COL_TIMESTAMP, System.currentTimeMillis());
- db.insert(TABLE_RECENT_CREATE, null, values);
- db.delete(TABLE_RECENT_CREATE, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null);
+ case URI_STATE:
+ final String authority = uri.getPathSegments().get(1);
+ final String rootId = uri.getPathSegments().get(2);
+ final String documentId = uri.getPathSegments().get(3);
+
+ final ContentValues key = new ContentValues();
+ key.put(StateColumns.AUTHORITY, authority);
+ key.put(StateColumns.ROOT_ID, rootId);
+ key.put(StateColumns.DOCUMENT_ID, documentId);
+
+ // Ensure that row exists, then update with changed values
+ db.insertWithOnConflict(TABLE_STATE, null, key, SQLiteDatabase.CONFLICT_IGNORE);
+ db.update(TABLE_STATE, values, StateColumns.AUTHORITY + "=? AND "
+ + StateColumns.ROOT_ID + "=? AND " + StateColumns.DOCUMENT_ID + "=?",
+ new String[] { authority, rootId, documentId });
+
return uri;
- }
- case URI_RESUME: {
+ case URI_RESUME:
final String packageName = uri.getPathSegments().get(1);
- values.put(COL_PACKAGE_NAME, packageName);
- values.put(COL_TIMESTAMP, System.currentTimeMillis());
+ values.put(ResumeColumns.PACKAGE_NAME, packageName);
+ values.put(ResumeColumns.TIMESTAMP, System.currentTimeMillis());
db.insert(TABLE_RESUME, null, values);
return uri;
- }
- default: {
+ default:
throw new UnsupportedOperationException("Unsupported Uri " + uri);
- }
}
}
@@ -190,12 +220,4 @@ public class RecentsProvider extends ContentProvider {
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Unsupported Uri " + uri);
}
-
- private static String buildWhereOlder(long deltaMillis) {
- return COL_TIMESTAMP + "<" + (System.currentTimeMillis() - deltaMillis);
- }
-
- private static String buildWhereYounger(long deltaMillis) {
- return COL_TIMESTAMP + ">" + (System.currentTimeMillis() - deltaMillis);
- }
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
index d4f1b39..8530a9f 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java
@@ -169,8 +169,9 @@ public class RootsCache {
if (state.localOnly && !localOnly) continue;
// Only include roots that serve requested content
- final boolean overlap = MimePredicate.mimeMatches(root.mimeTypes, state.acceptMimes)
- || MimePredicate.mimeMatches(state.acceptMimes, root.mimeTypes);
+ final boolean overlap =
+ MimePredicate.mimeMatches(root.derivedMimeTypes, state.acceptMimes) ||
+ MimePredicate.mimeMatches(state.acceptMimes, root.derivedMimeTypes);
if (!overlap) {
continue;
}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
index a1489a5..c69103e 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java
@@ -20,6 +20,8 @@ import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -32,15 +34,16 @@ import java.io.DataOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.ProtocolException;
-import java.util.Comparator;
/**
* Representation of a {@link Document}.
*/
-public class DocumentInfo implements Durable {
+public class DocumentInfo implements Durable, Parcelable {
private static final int VERSION_INIT = 1;
+ private static final int VERSION_SPLIT_URI = 2;
- public Uri uri;
+ public String authority;
+ public String documentId;
public String mimeType;
public String displayName;
public long lastModified;
@@ -49,13 +52,17 @@ public class DocumentInfo implements Durable {
public long size;
public int icon;
+ /** Derived fields that aren't persisted */
+ public Uri derivedUri;
+
public DocumentInfo() {
reset();
}
@Override
public void reset() {
- uri = null;
+ authority = null;
+ documentId = null;
mimeType = null;
displayName = null;
lastModified = -1;
@@ -63,6 +70,8 @@ public class DocumentInfo implements Durable {
summary = null;
size = -1;
icon = 0;
+
+ derivedUri = null;
}
@Override
@@ -70,8 +79,10 @@ public class DocumentInfo implements Durable {
final int version = in.readInt();
switch (version) {
case VERSION_INIT:
- final String rawUri = DurableUtils.readNullableString(in);
- uri = rawUri != null ? Uri.parse(rawUri) : null;
+ throw new ProtocolException("Ignored upgrade");
+ case VERSION_SPLIT_URI:
+ authority = DurableUtils.readNullableString(in);
+ documentId = DurableUtils.readNullableString(in);
mimeType = DurableUtils.readNullableString(in);
displayName = DurableUtils.readNullableString(in);
lastModified = in.readLong();
@@ -79,6 +90,7 @@ public class DocumentInfo implements Durable {
summary = DurableUtils.readNullableString(in);
size = in.readLong();
icon = in.readInt();
+ deriveFields();
break;
default:
throw new ProtocolException("Unknown version " + version);
@@ -87,8 +99,9 @@ public class DocumentInfo implements Durable {
@Override
public void write(DataOutputStream out) throws IOException {
- out.writeInt(VERSION_INIT);
- DurableUtils.writeNullableString(out, uri.toString());
+ out.writeInt(VERSION_SPLIT_URI);
+ DurableUtils.writeNullableString(out, authority);
+ DurableUtils.writeNullableString(out, documentId);
DurableUtils.writeNullableString(out, mimeType);
DurableUtils.writeNullableString(out, displayName);
out.writeLong(lastModified);
@@ -98,11 +111,41 @@ public class DocumentInfo implements Durable {
out.writeInt(icon);
}
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ DurableUtils.writeToParcel(dest, this);
+ }
+
+ public static final Creator<DocumentInfo> CREATOR = new Creator<DocumentInfo>() {
+ @Override
+ public DocumentInfo createFromParcel(Parcel in) {
+ final DocumentInfo doc = new DocumentInfo();
+ DurableUtils.readFromParcel(in, doc);
+ return doc;
+ }
+
+ @Override
+ public DocumentInfo[] newArray(int size) {
+ return new DocumentInfo[size];
+ }
+ };
+
public static DocumentInfo fromDirectoryCursor(Cursor cursor) {
- final DocumentInfo doc = new DocumentInfo();
final String authority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
- final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
- doc.uri = DocumentsContract.buildDocumentUri(authority, docId);
+ return fromCursor(cursor, authority);
+ }
+
+ public static DocumentInfo fromCursor(Cursor cursor, String authority) {
+ final DocumentInfo doc = new DocumentInfo();
+ doc.authority = authority;
+ doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
+ doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ doc.documentId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID);
doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
@@ -110,6 +153,7 @@ public class DocumentInfo implements Durable {
doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
+ doc.deriveFields();
return doc;
}
@@ -122,16 +166,7 @@ public class DocumentInfo implements Durable {
if (!cursor.moveToFirst()) {
throw new FileNotFoundException("Missing details for " + uri);
}
- final DocumentInfo doc = new DocumentInfo();
- doc.uri = uri;
- doc.mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- doc.displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
- doc.lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED);
- doc.flags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- doc.summary = getCursorString(cursor, Document.COLUMN_SUMMARY);
- doc.size = getCursorLong(cursor, Document.COLUMN_SIZE);
- doc.icon = getCursorInt(cursor, Document.COLUMN_ICON);
- return doc;
+ return fromCursor(cursor, uri.getAuthority());
} catch (Throwable t) {
throw asFileNotFoundException(t);
} finally {
@@ -140,9 +175,13 @@ public class DocumentInfo implements Durable {
}
}
+ private void deriveFields() {
+ derivedUri = DocumentsContract.buildDocumentUri(authority, documentId);
+ }
+
@Override
public String toString() {
- return "Document{name=" + displayName + ", uri=" + uri + "}";
+ return "Document{name=" + displayName + ", docId=" + documentId + "}";
}
public boolean isCreateSupported() {
@@ -189,42 +228,14 @@ public class DocumentInfo implements Durable {
}
}
+ /**
+ * Missing or null values are returned as 0.
+ */
public static int getCursorInt(Cursor cursor, String columnName) {
final int index = cursor.getColumnIndex(columnName);
return (index != -1) ? cursor.getInt(index) : 0;
}
- @Deprecated
- public static class DisplayNameComparator implements Comparator<DocumentInfo> {
- @Override
- public int compare(DocumentInfo lhs, DocumentInfo rhs) {
- final boolean leftDir = lhs.isDirectory();
- final boolean rightDir = rhs.isDirectory();
-
- if (leftDir != rightDir) {
- return leftDir ? -1 : 1;
- } else {
- return compareToIgnoreCaseNullable(lhs.displayName, rhs.displayName);
- }
- }
- }
-
- @Deprecated
- public static class LastModifiedComparator implements Comparator<DocumentInfo> {
- @Override
- public int compare(DocumentInfo lhs, DocumentInfo rhs) {
- return Long.compare(rhs.lastModified, lhs.lastModified);
- }
- }
-
- @Deprecated
- public static class SizeComparator implements Comparator<DocumentInfo> {
- @Override
- public int compare(DocumentInfo lhs, DocumentInfo rhs) {
- return Long.compare(rhs.size, lhs.size);
- }
- }
-
public static FileNotFoundException asFileNotFoundException(Throwable t)
throws FileNotFoundException {
if (t instanceof FileNotFoundException) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
index 33a1376..2541440 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java
@@ -16,8 +16,6 @@
package com.android.documentsui.model;
-import com.android.documentsui.RootsCache;
-
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
@@ -30,14 +28,13 @@ import java.util.LinkedList;
*/
public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
private static final int VERSION_INIT = 1;
+ private static final int VERSION_ADD_ROOT = 2;
- public RootInfo getRoot(RootsCache roots) {
- return roots.findRoot(getLast().uri);
- }
+ public RootInfo root;
- public String getTitle(RootsCache roots) {
- if (size() == 1) {
- return getRoot(roots).title;
+ public String getTitle() {
+ if (size() == 1 && root != null) {
+ return root.title;
} else if (size() > 1) {
return peek().displayName;
} else {
@@ -52,6 +49,7 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
@Override
public void reset() {
clear();
+ root = null;
}
@Override
@@ -59,6 +57,12 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
final int version = in.readInt();
switch (version) {
case VERSION_INIT:
+ throw new ProtocolException("Ignored upgrade");
+ case VERSION_ADD_ROOT:
+ if (in.readBoolean()) {
+ root = new RootInfo();
+ root.read(in);
+ }
final int size = in.readInt();
for (int i = 0; i < size; i++) {
final DocumentInfo doc = new DocumentInfo();
@@ -73,7 +77,13 @@ public class DocumentStack extends LinkedList<DocumentInfo> implements Durable {
@Override
public void write(DataOutputStream out) throws IOException {
- out.writeInt(VERSION_INIT);
+ out.writeInt(VERSION_ADD_ROOT);
+ if (root != null) {
+ out.writeBoolean(true);
+ root.write(out);
+ } else {
+ out.writeBoolean(false);
+ }
final int size = size();
out.writeInt(size);
for (int i = 0; i < size; i++) {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
index a6ddf70..e0e8acf 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java
@@ -23,28 +23,121 @@ import static com.android.documentsui.model.DocumentInfo.getCursorString;
import android.content.Context;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.provider.DocumentsContract.Root;
import com.android.documentsui.IconUtils;
import com.android.documentsui.R;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.net.ProtocolException;
import java.util.Objects;
/**
* Representation of a {@link Root}.
*/
-public class RootInfo {
+public class RootInfo implements Durable, Parcelable {
+ private static final int VERSION_INIT = 1;
+
public String authority;
public String rootId;
public int rootType;
public int flags;
public int icon;
- public int localIcon;
public String title;
public String summary;
public String documentId;
public long availableBytes;
- public String[] mimeTypes;
+ public String mimeTypes;
+
+ /** Derived fields that aren't persisted */
+ public String[] derivedMimeTypes;
+ public int derivedIcon;
+
+ public RootInfo() {
+ reset();
+ }
+
+ @Override
+ public void reset() {
+ authority = null;
+ rootId = null;
+ rootType = 0;
+ flags = 0;
+ icon = 0;
+ title = null;
+ summary = null;
+ documentId = null;
+ availableBytes = -1;
+ mimeTypes = null;
+
+ derivedMimeTypes = null;
+ derivedIcon = 0;
+ }
+
+ @Override
+ public void read(DataInputStream in) throws IOException {
+ final int version = in.readInt();
+ switch (version) {
+ case VERSION_INIT:
+ authority = DurableUtils.readNullableString(in);
+ rootId = DurableUtils.readNullableString(in);
+ rootType = in.readInt();
+ flags = in.readInt();
+ icon = in.readInt();
+ title = DurableUtils.readNullableString(in);
+ summary = DurableUtils.readNullableString(in);
+ documentId = DurableUtils.readNullableString(in);
+ availableBytes = in.readLong();
+ mimeTypes = DurableUtils.readNullableString(in);
+ deriveFields();
+ break;
+ default:
+ throw new ProtocolException("Unknown version " + version);
+ }
+ }
+
+ @Override
+ public void write(DataOutputStream out) throws IOException {
+ out.writeInt(VERSION_INIT);
+ DurableUtils.writeNullableString(out, authority);
+ DurableUtils.writeNullableString(out, rootId);
+ out.writeInt(rootType);
+ out.writeInt(flags);
+ out.writeInt(icon);
+ DurableUtils.writeNullableString(out, title);
+ DurableUtils.writeNullableString(out, summary);
+ DurableUtils.writeNullableString(out, documentId);
+ out.writeLong(availableBytes);
+ DurableUtils.writeNullableString(out, mimeTypes);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ DurableUtils.writeToParcel(dest, this);
+ }
+
+ public static final Creator<RootInfo> CREATOR = new Creator<RootInfo>() {
+ @Override
+ public RootInfo createFromParcel(Parcel in) {
+ final RootInfo root = new RootInfo();
+ DurableUtils.readFromParcel(in, root);
+ return root;
+ }
+
+ @Override
+ public RootInfo[] newArray(int size) {
+ return new RootInfo[size];
+ }
+ };
public static RootInfo fromRootsCursor(String authority, Cursor cursor) {
final RootInfo root = new RootInfo();
@@ -57,31 +150,38 @@ public class RootInfo {
root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY);
root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID);
root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES);
+ root.mimeTypes = getCursorString(cursor, Root.COLUMN_MIME_TYPES);
+ root.deriveFields();
+ return root;
+ }
- final String raw = getCursorString(cursor, Root.COLUMN_MIME_TYPES);
- root.mimeTypes = (raw != null) ? raw.split("\n") : null;
+ private void deriveFields() {
+ derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
// TODO: remove these special case icons
if ("com.android.externalstorage.documents".equals(authority)) {
- root.localIcon = R.drawable.ic_root_sdcard;
+ derivedIcon = R.drawable.ic_root_sdcard;
}
if ("com.android.providers.downloads.documents".equals(authority)) {
- root.localIcon = R.drawable.ic_root_download;
+ derivedIcon = R.drawable.ic_root_download;
}
if ("com.android.providers.media.documents".equals(authority)) {
- if ("image".equals(root.rootId)) {
- root.localIcon = R.drawable.ic_doc_image;
- } else if ("audio".equals(root.rootId)) {
- root.localIcon = R.drawable.ic_doc_audio;
+ if ("image".equals(rootId)) {
+ derivedIcon = R.drawable.ic_doc_image;
+ } else if ("audio".equals(rootId)) {
+ derivedIcon = R.drawable.ic_doc_audio;
}
}
+ }
- return root;
+ @Override
+ public String toString() {
+ return "Root{title=" + title + ", rootId=" + rootId + "}";
}
public Drawable loadIcon(Context context) {
- if (localIcon != 0) {
- return context.getResources().getDrawable(localIcon);
+ if (derivedIcon != 0) {
+ return context.getResources().getDrawable(derivedIcon);
} else {
return IconUtils.loadPackageIcon(context, authority, icon);
}