diff options
6 files changed, 354 insertions, 168 deletions
diff --git a/api/current.txt b/api/current.txt index 6bf8fe0..5fd52e3 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20266,12 +20266,17 @@ package android.provider { public final class DocumentsContract { ctor public DocumentsContract(); + method public static android.net.Uri buildContentsUri(java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildContentsUri(android.net.Uri); method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildDocumentUri(android.net.Uri, java.lang.String); method public static android.net.Uri buildRootUri(java.lang.String, java.lang.String); method public static android.net.Uri buildRootsUri(java.lang.String); + method public static android.net.Uri buildSearchUri(java.lang.String, java.lang.String, java.lang.String, java.lang.String); method public static android.net.Uri buildSearchUri(android.net.Uri, java.lang.String); + method public static java.lang.String getDocId(android.net.Uri); + method public static java.lang.String getRootId(android.net.Uri); + method public static java.lang.String getSearchQuery(android.net.Uri); method public static android.graphics.Bitmap getThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point); method public static boolean renameDocument(android.content.ContentResolver, android.net.Uri, java.lang.String); field public static final java.lang.String EXTRA_HAS_MORE = "has_more"; diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index 30c9a0d..9c2bb49 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -34,6 +34,7 @@ import libcore.io.IoUtils; import java.io.IOException; import java.io.InputStream; +import java.util.List; /** * The contract between a storage backend and the platform. Contains definitions @@ -152,36 +153,71 @@ public final class DocumentsContract { .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build(); } + /** + * Build URI representing the given {@link DocumentColumns#DOC_ID} in a + * storage root. + */ public static Uri buildDocumentUri(String authority, String rootId, String docId) { - return buildDocumentUri(buildRootUri(authority, rootId), docId); + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId) + .build(); } /** - * Build URI representing the given {@link DocumentColumns#DOC_ID} in a - * storage root. + * Build URI representing the contents of the given directory in a storage + * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}. */ - public static Uri buildDocumentUri(Uri rootUri, String docId) { - return rootUri.buildUpon().appendPath(PATH_DOCS).appendPath(docId).build(); + public static Uri buildContentsUri(String authority, String rootId, String docId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId) + .appendPath(PATH_CONTENTS).build(); } /** * Build URI representing a search for matching documents under a directory * in a storage backend. - * - * @param documentUri directory to search under, which must have - * {@link #FLAG_SUPPORTS_SEARCH}. */ - public static Uri buildSearchUri(Uri documentUri, String query) { - return documentUri.buildUpon() + public static Uri buildSearchUri(String authority, String rootId, String docId, String query) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId) .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build(); } - /** - * Build URI representing the contents of the given directory in a storage - * backend. The given document must be {@link #MIME_TYPE_DIRECTORY}. - */ - public static Uri buildContentsUri(Uri documentUri) { - return documentUri.buildUpon().appendPath(PATH_CONTENTS).build(); + public static Uri buildDocumentUri(Uri relatedUri, String docId) { + return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId); + } + + public static Uri buildContentsUri(Uri relatedUri) { + return buildContentsUri( + relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri)); + } + + public static Uri buildSearchUri(Uri relatedUri, String query) { + return buildSearchUri( + relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query); + } + + public static String getRootId(Uri documentUri) { + final List<String> paths = documentUri.getPathSegments(); + if (!PATH_ROOTS.equals(paths.get(0))) { + throw new IllegalArgumentException(); + } + return paths.get(1); + } + + public static String getDocId(Uri documentUri) { + final List<String> paths = documentUri.getPathSegments(); + if (!PATH_ROOTS.equals(paths.get(0))) { + throw new IllegalArgumentException(); + } + if (!PATH_DOCS.equals(paths.get(2))) { + throw new IllegalArgumentException(); + } + return paths.get(3); + } + + public static String getSearchQuery(Uri documentUri) { + return documentUri.getQueryParameter(PARAM_QUERY); } /** diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index ef97dd5..2740e53 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -67,31 +67,29 @@ public class DirectoryFragment extends Fragment { private AbsListView mCurrentView; + private static final int TYPE_NORMAL = 1; + private static final int TYPE_SEARCH = 2; + private static final int TYPE_RECENT_OPEN = 3; + private static final int TYPE_RECENT_CREATE = 4; + + private int mType = TYPE_NORMAL; + private DocumentsAdapter mAdapter; private LoaderCallbacks<Cursor> mCallbacks; - private int mFlags; - - private static final String EXTRA_ROOT_URI = "rootUri"; - private static final String EXTRA_DOCS_URI = "docsUri"; + private static final String EXTRA_URI = "uri"; private static final int LOADER_DOCUMENTS = 2; - public static void show(FragmentManager fm, Uri rootUri, Uri docsUri, String displayName, - boolean addToBackStack) { + public static void show(FragmentManager fm, Uri uri) { final Bundle args = new Bundle(); - args.putParcelable(EXTRA_ROOT_URI, rootUri); - args.putParcelable(EXTRA_DOCS_URI, docsUri); + args.putParcelable(EXTRA_URI, uri); final DirectoryFragment fragment = new DirectoryFragment(); fragment.setArguments(args); final FragmentTransaction ft = fm.beginTransaction(); ft.replace(R.id.directory, fragment); - if (addToBackStack) { - ft.addToBackStack(displayName); - } - ft.setBreadCrumbTitle(displayName); ft.commitAllowingStateLoss(); } @@ -119,9 +117,17 @@ public class DirectoryFragment extends Fragment { mAdapter = new DocumentsAdapter(context); updateMode(); - // TODO: migrate flags query to loader - final Uri docsUri = getArguments().getParcelable(EXTRA_DOCS_URI); - mFlags = getDocumentFlags(context, docsUri); + final Uri uri = getArguments().getParcelable(EXTRA_URI); + + if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) { + mType = TYPE_SEARCH; + } else if (RecentsProvider.buildRecentOpen().equals(uri)) { + mType = TYPE_RECENT_OPEN; + } else if (RecentsProvider.buildRecentCreate().equals(uri)) { + mType = TYPE_RECENT_CREATE; + } else { + mType = TYPE_NORMAL; + } mCallbacks = new LoaderCallbacks<Cursor>() { @Override @@ -137,10 +143,10 @@ public class DirectoryFragment extends Fragment { } final Uri contentsUri; - if (docsUri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) { - contentsUri = docsUri; + if (mType == TYPE_NORMAL) { + contentsUri = DocumentsContract.buildContentsUri(uri); } else { - contentsUri = DocumentsContract.buildContentsUri(docsUri); + contentsUri = uri; } return new CursorLoader(context, contentsUri, null, null, null, sortOrder); @@ -164,10 +170,6 @@ public class DirectoryFragment extends Fragment { public void onStart() { super.onStart(); getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks); - - // TODO: clean up tracking of current directory - final Uri docsUri = getArguments().getParcelable(EXTRA_DOCS_URI); - ((DocumentsActivity) getActivity()).onDirectoryChanged(docsUri, mFlags); } @Override @@ -249,8 +251,8 @@ public class DirectoryFragment extends Fragment { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final Cursor cursor = (Cursor) mAdapter.getItem(position); - final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI); - final Document doc = Document.fromCursor(rootUri, cursor); + final Uri uri = getArguments().getParcelable(EXTRA_URI); + final Document doc = Document.fromCursor(uri, cursor); ((DocumentsActivity) getActivity()).onDocumentPicked(doc); } }; @@ -270,7 +272,7 @@ public class DirectoryFragment extends Fragment { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { if (item.getItemId() == R.id.menu_open) { - final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI); + final Uri uri = getArguments().getParcelable(EXTRA_URI); final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); final ArrayList<Document> docs = Lists.newArrayList(); @@ -278,7 +280,7 @@ public class DirectoryFragment extends Fragment { for (int i = 0; i < size; i++) { if (checked.valueAt(i)) { final Cursor cursor = (Cursor) mAdapter.getItem(checked.keyAt(i)); - docs.add(Document.fromCursor(rootUri, cursor)); + docs.add(Document.fromCursor(uri, cursor)); } } @@ -346,15 +348,13 @@ public class DirectoryFragment extends Fragment { final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); - final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI); - final String authority = rootUri.getAuthority(); - + final Uri uri = getArguments().getParcelable(EXTRA_URI); if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) { - final Uri childUri = DocumentsContract.buildDocumentUri(rootUri, docId); + final Uri childUri = DocumentsContract.buildDocumentUri(uri, docId); icon.setImageURI(childUri); } else { - icon.setImageDrawable( - DocumentsActivity.resolveDocumentIcon(context, authority, mimeType)); + icon.setImageDrawable(DocumentsActivity.resolveDocumentIcon( + context, uri.getAuthority(), mimeType)); } title.setText(displayName); @@ -364,20 +364,6 @@ public class DirectoryFragment extends Fragment { } } - private static int getDocumentFlags(Context context, Uri uri) { - final Cursor cursor = context.getContentResolver().query(uri, new String[] { - DocumentColumns.FLAGS }, null, null, null); - try { - if (cursor.moveToFirst()) { - return getCursorInt(cursor, DocumentColumns.FLAGS); - } else { - return 0; - } - } finally { - cursor.close(); - } - } - public static String getCursorString(Cursor cursor, String columnName) { return cursor.getString(cursor.getColumnIndex(columnName)); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 405ef36..8f2e61d 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import static com.android.documentsui.DirectoryFragment.getCursorInt; +import static com.android.documentsui.DirectoryFragment.getCursorLong; import static com.android.documentsui.DirectoryFragment.getCursorString; import android.app.ActionBar; @@ -25,7 +27,6 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; -import android.app.FragmentManager.OnBackStackChangedListener; import android.content.ClipData; import android.content.ContentResolver; import android.content.ContentValues; @@ -78,6 +79,8 @@ import com.google.android.collect.Maps; import libcore.io.IoUtils; +import org.json.JSONArray; +import org.json.JSONException; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -85,12 +88,13 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; public class DocumentsActivity extends Activity { private static final String TAG = "Documents"; - // TODO: fragment to show recently opened documents + // TODO: share backend root cache with recents provider private static final String TAG_CREATE_DIRECTORY = "create_directory"; @@ -106,18 +110,17 @@ public class DocumentsActivity extends Activity { private ActionBarDrawerToggle mDrawerToggle; private static HashMap<String, DocumentsProviderInfo> sProviders = Maps.newHashMap(); - private static ArrayList<Root> sRoots = Lists.newArrayList(); + private static HashMap<String, Root> sRoots = Maps.newHashMap(); + + // TODO: remove once adapter split by type + private static ArrayList<Root> sRootsList = Lists.newArrayList(); private RootsAdapter mRootsAdapter; private ListView mRootsList; private final DisplayState mDisplayState = new DisplayState(); - private Root mCurrentRoot; - - private Uri mCurrentDir; - private boolean mCurrentSupportsCreate; - private boolean mCurrentSupportsSearch; + private LinkedList<Document> mStack = new LinkedList<Document>(); @Override public void onCreate(Bundle icicle) { @@ -148,8 +151,6 @@ public class DocumentsActivity extends Activity { setResult(Activity.RESULT_CANCELED); setContentView(R.layout.activity); - getFragmentManager().addOnBackStackChangedListener(mStackListener); - if (mAction == ACTION_CREATE) { final String mimeType = getIntent().getType(); final String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); @@ -157,7 +158,7 @@ public class DocumentsActivity extends Activity { } mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); - mRootsAdapter = new RootsAdapter(this, sRoots); + mRootsAdapter = new RootsAdapter(this, sRootsList); mRootsList = (ListView) findViewById(R.id.roots_list); mRootsList.setAdapter(mRootsAdapter); mRootsList.setOnItemClickListener(mRootsListener); @@ -168,10 +169,24 @@ public class DocumentsActivity extends Activity { mDrawerLayout.setDrawerListener(mDrawerListener); mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START); - mDrawerLayout.openDrawer(mRootsList); - - updateActionBar(); updateRoots(); + + // Restore last stack for calling package + // TODO: move into async loader + final String packageName = getCallingPackage(); + final Cursor cursor = getContentResolver() + .query(RecentsProvider.buildResume(packageName), null, null, null, null); + try { + if (cursor.moveToFirst()) { + final String rawStack = cursor.getString( + cursor.getColumnIndex(RecentsProvider.COL_PATH)); + restoreStack(rawStack); + } + } finally { + cursor.close(); + } + + updateDirectoryFragment(); } private DrawerListener mDrawerListener = new DrawerListener() { @@ -205,7 +220,6 @@ public class DocumentsActivity extends Activity { } public void updateActionBar() { - final FragmentManager fm = getFragmentManager(); final ActionBar actionBar = getActionBar(); actionBar.setDisplayShowHomeEnabled(true); @@ -223,13 +237,12 @@ public class DocumentsActivity extends Activity { } else { actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_LIST); - if (mCurrentRoot != null) { - actionBar.setIcon(mCurrentRoot.icon); - } + final Root root = getCurrentRoot(); + actionBar.setIcon(root != null ? root.icon : null); actionBar.setTitle(null); actionBar.setListNavigationCallbacks(mSortAdapter, mSortListener); - if (fm.getBackStackEntryCount() > 0) { + if (mStack.size() > 1) { mDrawerToggle.setDrawerIndicatorEnabled(false); } else { mDrawerToggle.setDrawerIndicatorEnabled(true); @@ -247,10 +260,10 @@ public class DocumentsActivity extends Activity { mSearchView.setOnQueryTextListener(new OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - // TODO: clear existing directory stack? - final Uri searchUri = DocumentsContract.buildSearchUri(mCurrentDir, query); - DirectoryFragment.show( - getFragmentManager(), mCurrentRoot.rootUri, searchUri, query, true); + // TODO: use second directory stack for searches? + final Document cwd = getCurrentDirectory(); + final Document searchDoc = Document.fromSearch(cwd.uri, query); + onDocumentPicked(searchDoc); mSearchView.setIconified(true); return true; } @@ -268,13 +281,20 @@ public class DocumentsActivity extends Activity { public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); + final Document cwd = getCurrentDirectory(); + final MenuItem createDir = menu.findItem(R.id.menu_create_dir); createDir.setVisible(mAction == ACTION_CREATE); - createDir.setEnabled(mCurrentSupportsCreate); + createDir.setEnabled(cwd != null && cwd.isCreateSupported()); // TODO: close any search in-progress when hiding final MenuItem search = menu.findItem(R.id.menu_search); - search.setVisible(mCurrentSupportsSearch); + search.setVisible(cwd != null && cwd.isSearchSupported()); + + if (mAction == ACTION_CREATE) { + final FragmentManager fm = getFragmentManager(); + SaveFragment.get(fm).setSaveEnabled(cwd != null && cwd.isCreateSupported()); + } return true; } @@ -287,8 +307,7 @@ public class DocumentsActivity extends Activity { final int id = item.getItemId(); if (id == android.R.id.home) { - getFragmentManager().popBackStack(); - updateActionBar(); + onBackPressed(); return true; } else if (id == R.id.menu_create_dir) { CreateDirectoryFragment.show(getFragmentManager()); @@ -299,12 +318,19 @@ public class DocumentsActivity extends Activity { return super.onOptionsItemSelected(item); } - private OnBackStackChangedListener mStackListener = new OnBackStackChangedListener() { - @Override - public void onBackStackChanged() { - updateActionBar(); + @Override + public void onBackPressed() { + final int size = mStack.size(); + if (size > 1) { + mStack.pop(); + updateDirectoryFragment(); + } else if (size == 1 && !mDrawerLayout.isDrawerOpen(mRootsList)) { + // TODO: open root drawer once we can capture back key + super.onBackPressed(); + } else { + super.onBackPressed(); } - }; + } // TODO: support additional sort orders private BaseAdapter mSortAdapter = new BaseAdapter() { @@ -340,12 +366,9 @@ public class DocumentsActivity extends Activity { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - final FragmentManager fm = getFragmentManager(); - final int count = fm.getBackStackEntryCount(); - if (count > 0) { - title.setText(fm.getBackStackEntryAt(count - 1).getBreadCrumbTitle()); - } else if (mCurrentRoot != null) { - title.setText(mCurrentRoot.title); + final Document cwd = getCurrentDirectory(); + if (cwd != null) { + title.setText(cwd.displayName); } else { title.setText(null); } @@ -377,28 +400,42 @@ public class DocumentsActivity extends Activity { } }; + public Root getCurrentRoot() { + final Document cwd = getCurrentDirectory(); + if (cwd != null) { + return sRoots.get(DocumentsContract.getRootId(cwd.uri)); + } else { + return null; + } + } + + public Document getCurrentDirectory() { + return mStack.peek(); + } + public DisplayState getDisplayState() { return mDisplayState; } - public void onDirectoryChanged(Uri uri, int flags) { - mCurrentDir = uri; - mCurrentSupportsCreate = (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; - mCurrentSupportsSearch = (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0; - - if (mAction == ACTION_CREATE) { - final FragmentManager fm = getFragmentManager(); - SaveFragment.get(fm).setSaveEnabled(mCurrentSupportsCreate); + private void updateDirectoryFragment() { + final FragmentManager fm = getFragmentManager(); + final Document cwd = getCurrentDirectory(); + if (cwd != null) { + DirectoryFragment.show(fm, cwd.uri); + mDrawerLayout.closeDrawer(mRootsList); + } else { + mDrawerLayout.openDrawer(mRootsList); } - + updateActionBar(); invalidateOptionsMenu(); + dumpStack(); } public void onDocumentPicked(Document doc) { final FragmentManager fm = getFragmentManager(); if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) { - // Nested directory picked, recurse using new fragment - DirectoryFragment.show(fm, doc.rootUri, doc.uri, doc.displayName, true); + mStack.push(doc); + updateDirectoryFragment(); } else if (mAction == ACTION_OPEN) { // Explicit file picked, return onFinished(doc.uri); @@ -424,22 +461,70 @@ public class DocumentsActivity extends Activity { values.put(DocumentColumns.MIME_TYPE, mimeType); values.put(DocumentColumns.DISPLAY_NAME, displayName); - final Uri uri = getContentResolver().insert(mCurrentDir, values); - if (uri != null) { - onFinished(uri); + final Document cwd = getCurrentDirectory(); + final Uri childUri = getContentResolver().insert(cwd.uri, values); + if (childUri != null) { + onFinished(childUri); } else { Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); } } + private String saveStack() { + final JSONArray stack = new JSONArray(); + for (int i = 0; i < mStack.size(); i++) { + stack.put(mStack.get(i).uri); + } + return stack.toString(); + } + + private void restoreStack(String rawStack) { + Log.d(TAG, "restoreStack: " + rawStack); + mStack.clear(); + try { + final JSONArray stack = new JSONArray(rawStack); + for (int i = 0; i < stack.length(); i++) { + final Uri uri = Uri.parse(stack.getString(i)); + final Document doc = Document.fromUri(getContentResolver(), uri); + mStack.add(doc); + } + } catch (JSONException e) { + Log.w(TAG, "Failed to decode stack", e); + } + } + private void onFinished(Uri... uris) { Log.d(TAG, "onFinished() " + Arrays.toString(uris)); + final ContentResolver resolver = getContentResolver(); + final ContentValues values = new ContentValues(); + + final String stack = saveStack(); + if (mAction == ACTION_CREATE) { + // Remember stack for last create + values.clear(); + values.put(RecentsProvider.COL_PATH, stack); + resolver.insert(RecentsProvider.buildRecentCreate(), values); + + } else if (mAction == ACTION_OPEN) { + // Remember opened items + for (Uri uri : uris) { + values.clear(); + values.put(RecentsProvider.COL_URI, uri.toString()); + resolver.insert(RecentsProvider.buildRecentOpen(), values); + } + } + + // Remember location for next app launch + final String packageName = getCallingPackage(); + values.clear(); + values.put(RecentsProvider.COL_PATH, stack); + resolver.insert(RecentsProvider.buildResume(packageName), values); + final Intent intent = new Intent(); if (uris.length == 1) { intent.setData(uris[0]); } else if (uris.length > 1) { - final ContentResolver resolver = getContentResolver(); final ClipData clipData = new ClipData(null, mAcceptMimes, new ClipData.Item(uris[0])); for (int i = 1; i < uris.length; i++) { clipData.addItem(new ClipData.Item(uris[i])); @@ -470,8 +555,8 @@ public class DocumentsActivity extends Activity { public static class Root { public DocumentsProviderInfo info; + public String rootId; public int rootType; - public Uri rootUri; public Uri uri; public Drawable icon; public String title; @@ -479,21 +564,18 @@ public class DocumentsActivity extends Activity { public static Root fromCursor( Context context, DocumentsProviderInfo info, Cursor cursor) { - final String rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID)); - - final Root root = new Root(); final PackageManager pm = context.getPackageManager(); + final Root root = new Root(); root.info = info; + root.rootId = cursor.getString(cursor.getColumnIndex(RootColumns.ROOT_ID)); root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE)); - root.rootUri = DocumentsContract.buildRootUri(info.providerInfo.authority, rootId); root.uri = DocumentsContract.buildDocumentUri( - root.rootUri, DocumentsContract.ROOT_DOC_ID); + info.providerInfo.authority, root.rootId, DocumentsContract.ROOT_DOC_ID); root.icon = info.providerInfo.loadIcon(pm); root.title = info.providerInfo.loadLabel(pm).toString(); root.summary = null; - final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON)); if (icon != 0) { try { @@ -529,24 +611,28 @@ public class DocumentsActivity extends Activity { } public static class Document { - public Uri rootUri; public Uri uri; public String mimeType; public String displayName; + public long lastModified; + public int flags; - public static Document fromCursor(Uri rootUri, Cursor cursor) { - final Document doc = new Document(); + public static Document fromCursor(Uri parent, Cursor cursor) { + final String authority = parent.getAuthority(); + final String rootId = DocumentsContract.getRootId(parent); final String docId = getCursorString(cursor, DocumentColumns.DOC_ID); - doc.rootUri = rootUri; - doc.uri = DocumentsContract.buildDocumentUri(rootUri, docId); + + final Document doc = new Document(); + doc.uri = DocumentsContract.buildDocumentUri(authority, rootId, docId); doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); + doc.lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); + doc.flags = getCursorInt(cursor, DocumentColumns.FLAGS); return doc; } - public static Document fromUri(ContentResolver resolver, Uri rootUri, Uri uri) { + public static Document fromUri(ContentResolver resolver, Uri uri) { final Document doc = new Document(); - doc.rootUri = rootUri; doc.uri = uri; final Cursor cursor = resolver.query(uri, null, null, null, null); @@ -556,12 +642,41 @@ public class DocumentsActivity extends Activity { } doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); + doc.lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); + doc.flags = getCursorInt(cursor, DocumentColumns.FLAGS); } finally { cursor.close(); } return doc; } + + public static Document fromSearch(Uri relatedUri, String query) { + final Document doc = new Document(); + doc.uri = DocumentsContract.buildSearchUri(relatedUri, query); + doc.mimeType = DocumentsContract.MIME_TYPE_DIRECTORY; + doc.displayName = query; + doc.lastModified = System.currentTimeMillis(); + doc.flags = 0; + return doc; + } + + @Override + public String toString() { + return "'" + displayName + "' " + uri; + } + + public boolean isCreateSupported() { + return (flags & DocumentsContract.FLAG_SUPPORTS_CREATE) != 0; + } + + public boolean isSearchSupported() { + return (flags & DocumentsContract.FLAG_SUPPORTS_SEARCH) != 0; + } + + public boolean isThumbnailSupported() { + return (flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0; + } } public static boolean mimeMatches(String filter, String[] tests) { @@ -622,6 +737,7 @@ public class DocumentsActivity extends Activity { private void updateRoots() { sProviders.clear(); sRoots.clear(); + sRootsList.clear(); final PackageManager pm = getPackageManager(); final List<ProviderInfo> providers = pm.queryContentProviders( @@ -643,7 +759,9 @@ public class DocumentsActivity extends Activity { final Cursor cursor = getContentResolver().query(uri, null, null, null, null); try { while (cursor.moveToNext()) { - sRoots.add(Root.fromCursor(this, info, cursor)); + final Root root = Root.fromCursor(this, info, cursor); + sRoots.put(root.rootId, root); + sRootsList.add(root); } } finally { cursor.close(); @@ -710,19 +828,25 @@ public class DocumentsActivity extends Activity { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // Clear entire backstack and start in new root - final FragmentManager fm = getFragmentManager(); - while (fm.getBackStackEntryCount() > 0) { - fm.popBackStackImmediate(); - } + mStack.clear(); + + final Root root = mRootsAdapter.getItem(position); - mCurrentRoot = mRootsAdapter.getItem(position); - DirectoryFragment.show(getFragmentManager(), mCurrentRoot.rootUri, mCurrentRoot.uri, - mCurrentRoot.title, false); + final ContentResolver resolver = getContentResolver(); + final Document doc = Document.fromUri(resolver, root.uri); + onDocumentPicked(doc); mDrawerLayout.closeDrawers(); } }; + private void dumpStack() { + Log.d(TAG, "Current stack:"); + for (Document doc : mStack) { + Log.d(TAG, "--> " + doc); + } + } + public static class RootsAdapter extends ArrayAdapter<Root> { public RootsAdapter(Context context, List<Root> list) { super(context, android.R.layout.simple_list_item_1, list); @@ -780,12 +904,13 @@ public class DocumentsActivity extends Activity { values.put(DocumentColumns.DISPLAY_NAME, displayName); final DocumentsActivity activity = (DocumentsActivity) getActivity(); - final Uri uri = resolver.insert(activity.mCurrentDir, values); - if (uri != null) { + final Document cwd = activity.getCurrentDirectory(); + + final Uri childUri = resolver.insert(cwd.uri, values); + if (childUri != null) { // Navigate into newly created child - final Document doc = Document.fromUri( - resolver, activity.mCurrentRoot.rootUri, uri); - activity.onDocumentPicked(doc); + final Document childDoc = Document.fromUri(resolver, childUri); + activity.onDocumentPicked(childDoc); } else { Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show(); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index e6ee614..dbcb039 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -17,6 +17,7 @@ package com.android.documentsui; import android.content.ContentProvider; +import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.UriMatcher; @@ -30,6 +31,9 @@ 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 String AUTHORITY = "com.android.documentsui.recents"; private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); @@ -53,13 +57,29 @@ public class RecentsProvider extends ContentProvider { * 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"; + public static Uri buildRecentOpen() { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY).appendPath("recent_open").build(); + } + + public static Uri buildRecentCreate() { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY).appendPath("recent_create").build(); + } + + public static Uri buildResume(String packageName) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(AUTHORITY).appendPath("resume").appendPath(packageName).build(); + } + private DatabaseHelper mHelper; private static class DatabaseHelper extends SQLiteOpenHelper { - private static final String DB_NAME = "recents"; + private static final String DB_NAME = "recents.db"; private static final int VERSION_INIT = 1; @@ -70,19 +90,19 @@ public class RecentsProvider extends ContentProvider { @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_RECENT_OPEN + " (" + - COL_PATH + " TEXT," + - COL_TIMESTAMP + " INTEGER," + + COL_URI + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + + COL_TIMESTAMP + " INTEGER" + ")"); db.execSQL("CREATE TABLE " + TABLE_RECENT_CREATE + " (" + - COL_PATH + " TEXT," + - COL_TIMESTAMP + " INTEGER," + + COL_PATH + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + + COL_TIMESTAMP + " INTEGER" + ")"); db.execSQL("CREATE TABLE " + TABLE_RESUME + " (" + COL_PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," + COL_PATH + " TEXT," + - COL_TIMESTAMP + " INTEGER," + + COL_TIMESTAMP + " INTEGER" + ")"); } @@ -136,11 +156,13 @@ public class RecentsProvider extends ContentProvider { 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); 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); return uri; @@ -148,6 +170,7 @@ public class RecentsProvider extends ContentProvider { case URI_RESUME: { final String packageName = uri.getPathSegments().get(1); values.put(COL_PACKAGE_NAME, packageName); + values.put(COL_TIMESTAMP, System.currentTimeMillis()); db.insert(TABLE_RESUME, null, values); return uri; } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index dd7472b..5c12484 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -110,15 +110,15 @@ public class ExternalStorageProvider extends ContentProvider { return cursor; } case URI_ROOTS_ID: { - final String root = uri.getPathSegments().get(1); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); final MatrixCursor cursor = new MatrixCursor(rootsProjection); - includeRoot(cursor, mRoots.get(root)); + includeRoot(cursor, root); return cursor; } case URI_DOCS_ID: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); final MatrixCursor cursor = new MatrixCursor(docsProjection); final File file = docIdToFile(root, docId); @@ -126,20 +126,22 @@ public class ExternalStorageProvider extends ContentProvider { return cursor; } case URI_DOCS_ID_CONTENTS: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); final MatrixCursor cursor = new MatrixCursor(docsProjection); final File parent = docIdToFile(root, docId); + for (File file : parent.listFiles()) { includeFile(cursor, root, file); } + return cursor; } case URI_DOCS_ID_SEARCH: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); - final String query = uri.getQueryParameter(DocumentsContract.PARAM_QUERY).toLowerCase(); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); + final String query = DocumentsContract.getSearchQuery(uri).toLowerCase(); final MatrixCursor cursor = new MatrixCursor(docsProjection); final File parent = docIdToFile(root, docId); @@ -158,6 +160,7 @@ public class ExternalStorageProvider extends ContentProvider { } } } + return cursor; } default: { @@ -218,16 +221,24 @@ public class ExternalStorageProvider extends ContentProvider { final String docId = fileToDocId(root, file); final long id = docId.hashCode(); + + final String displayName; + if (DocumentsContract.ROOT_DOC_ID.equals(docId)) { + displayName = root.title; + } else { + displayName = file.getName(); + } + cursor.addRow(new Object[] { - id, file.getName(), file.length(), docId, mimeType, file.lastModified(), flags }); + id, displayName, file.length(), docId, mimeType, file.lastModified(), flags }); } @Override public String getType(Uri uri) { switch (sMatcher.match(uri)) { case URI_DOCS_ID: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); return getTypeForFile(docIdToFile(root, docId)); } default: { @@ -261,8 +272,8 @@ public class ExternalStorageProvider extends ContentProvider { public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { switch (sMatcher.match(uri)) { case URI_DOCS_ID: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); // TODO: offer as thumbnail final File file = docIdToFile(root, docId); @@ -278,8 +289,8 @@ public class ExternalStorageProvider extends ContentProvider { public Uri insert(Uri uri, ContentValues values) { switch (sMatcher.match(uri)) { case URI_DOCS_ID: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); final File parent = docIdToFile(root, docId); @@ -317,8 +328,8 @@ public class ExternalStorageProvider extends ContentProvider { public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { switch (sMatcher.match(uri)) { case URI_DOCS_ID: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); final File file = docIdToFile(root, docId); final File newFile = new File( @@ -335,8 +346,8 @@ public class ExternalStorageProvider extends ContentProvider { public int delete(Uri uri, String selection, String[] selectionArgs) { switch (sMatcher.match(uri)) { case URI_DOCS_ID: { - final Root root = mRoots.get(uri.getPathSegments().get(1)); - final String docId = uri.getPathSegments().get(3); + final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + final String docId = DocumentsContract.getDocId(uri); final File file = docIdToFile(root, docId); return file.delete() ? 1 : 0; |