summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-08-03 00:43:00 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2013-08-03 00:43:00 +0000
commitcf9d87c68874e6008399fa1f571dbff34cf3fc82 (patch)
tree1db75a9414017b977d98d4603293dfbaa4d8c403
parentc5e32ef628c23758523008358c11aaa7e7422ac4 (diff)
parentdc2963aecaf38bf53d6de82957412a486049c207 (diff)
downloadframeworks_base-cf9d87c68874e6008399fa1f571dbff34cf3fc82.zip
frameworks_base-cf9d87c68874e6008399fa1f571dbff34cf3fc82.tar.gz
frameworks_base-cf9d87c68874e6008399fa1f571dbff34cf3fc82.tar.bz2
Merge "Track and persist directory stacks; recents work."
-rw-r--r--api/current.txt5
-rw-r--r--core/java/android/provider/DocumentsContract.java68
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java78
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java285
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java35
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java51
6 files changed, 354 insertions, 168 deletions
diff --git a/api/current.txt b/api/current.txt
index 18c6938..32fa009 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20270,12 +20270,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;