diff options
15 files changed, 692 insertions, 281 deletions
diff --git a/api/current.txt b/api/current.txt index c7b3ed9..5ec1771 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20796,6 +20796,7 @@ package android.provider { field public static final java.lang.String COLUMN_SIZE = "_size"; field public static final java.lang.String COLUMN_SUMMARY = "summary"; field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20 + field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 64; // 0x40 field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10 field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 5333a25..eaa4f78 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -251,6 +251,15 @@ public final class DocumentsContract { * @see #COLUMN_FLAGS */ public static final int FLAG_DIR_PREFERS_GRID = 1 << 5; + + /** + * Flag indicating that a directory prefers its contents be sorted by + * {@link #COLUMN_LAST_MODIFIED}. Only valid when + * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + */ + public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 6; } /** @@ -292,9 +301,6 @@ public final class DocumentsContract { * @see #FLAG_LOCAL_ONLY * @see #FLAG_SUPPORTS_CREATE * @see #FLAG_ADVANCED - * @see #FLAG_PROVIDES_AUDIO - * @see #FLAG_PROVIDES_IMAGES - * @see #FLAG_PROVIDES_VIDEO */ public static final String COLUMN_FLAGS = "flags"; 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); } |
