Merge "Remember mode and sort on per-directory basis." into klp-dev
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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--) {
|
||||
|
||||
@@ -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";
|
||||
public static final String TABLE_RECENT = "recent";
|
||||
public static final String TABLE_STATE = "state";
|
||||
public 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 class RecentColumns {
|
||||
public static final String STACK = "stack";
|
||||
public static final String TIMESTAMP = "timestamp";
|
||||
}
|
||||
|
||||
public static Uri buildRecentCreate() {
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
final String raw = getCursorString(cursor, Root.COLUMN_MIME_TYPES);
|
||||
root.mimeTypes = (raw != null) ? raw.split("\n") : null;
|
||||
|
||||
// TODO: remove these special case icons
|
||||
if ("com.android.externalstorage.documents".equals(authority)) {
|
||||
root.localIcon = R.drawable.ic_root_sdcard;
|
||||
}
|
||||
if ("com.android.providers.downloads.documents".equals(authority)) {
|
||||
root.localIcon = 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;
|
||||
}
|
||||
}
|
||||
|
||||
root.mimeTypes = getCursorString(cursor, Root.COLUMN_MIME_TYPES);
|
||||
root.deriveFields();
|
||||
return root;
|
||||
}
|
||||
|
||||
private void deriveFields() {
|
||||
derivedMimeTypes = (mimeTypes != null) ? mimeTypes.split("\n") : null;
|
||||
|
||||
// TODO: remove these special case icons
|
||||
if ("com.android.externalstorage.documents".equals(authority)) {
|
||||
derivedIcon = R.drawable.ic_root_sdcard;
|
||||
}
|
||||
if ("com.android.providers.downloads.documents".equals(authority)) {
|
||||
derivedIcon = R.drawable.ic_root_download;
|
||||
}
|
||||
if ("com.android.providers.media.documents".equals(authority)) {
|
||||
if ("image".equals(rootId)) {
|
||||
derivedIcon = R.drawable.ic_doc_image;
|
||||
} else if ("audio".equals(rootId)) {
|
||||
derivedIcon = R.drawable.ic_doc_audio;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user