summaryrefslogtreecommitdiffstats
path: root/packages/DocumentsUI/src/com
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-08-02 10:33:21 -0700
committerJeff Sharkey <jsharkey@android.com>2013-08-02 11:05:11 -0700
commit92d7e697a864a3e18bef4ef256bb3eb339a66b4e (patch)
treee5cce35e071787bfb81a8341716071925f1c86c4 /packages/DocumentsUI/src/com
parent7e258b31e70464bb6d80b8b42f0cef8e4417bd6a (diff)
downloadframeworks_base-92d7e697a864a3e18bef4ef256bb3eb339a66b4e.zip
frameworks_base-92d7e697a864a3e18bef4ef256bb3eb339a66b4e.tar.gz
frameworks_base-92d7e697a864a3e18bef4ef256bb3eb339a66b4e.tar.bz2
Reference docs by ROOT_ID and DOC_ID; recents.
The same document may be present with different sematics under multiple storage roots, so always reference using both ROOT_ID and DOC_ID. This enables backends to revoke permissions for an entire root, such as when an account is removed. Start building provider to remember recently accessed documents. Change-Id: I75befa2e61393dec12fcc7fd27f631fcddae46fa
Diffstat (limited to 'packages/DocumentsUI/src/com')
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java42
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java80
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java177
-rw-r--r--packages/DocumentsUI/src/com/android/documentsui/TestActivity.java24
4 files changed, 263 insertions, 60 deletions
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index 1f22613..ef97dd5 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -59,6 +59,8 @@ import java.util.ArrayList;
public class DirectoryFragment extends Fragment {
// TODO: show storage backend in item views when requested
+ // TODO: apply sort order locally
+ // TODO: apply MIME filtering locally
private ListView mListView;
private GridView mGridView;
@@ -70,14 +72,16 @@ public class DirectoryFragment extends Fragment {
private int mFlags;
- private static final String EXTRA_URI = "uri";
+ private static final String EXTRA_ROOT_URI = "rootUri";
+ private static final String EXTRA_DOCS_URI = "docsUri";
private static final int LOADER_DOCUMENTS = 2;
- public static void show(
- FragmentManager fm, Uri uri, String displayName, boolean addToBackStack) {
+ public static void show(FragmentManager fm, Uri rootUri, Uri docsUri, String displayName,
+ boolean addToBackStack) {
final Bundle args = new Bundle();
- args.putParcelable(EXTRA_URI, uri);
+ args.putParcelable(EXTRA_ROOT_URI, rootUri);
+ args.putParcelable(EXTRA_DOCS_URI, docsUri);
final DirectoryFragment fragment = new DirectoryFragment();
fragment.setArguments(args);
@@ -116,8 +120,8 @@ public class DirectoryFragment extends Fragment {
updateMode();
// TODO: migrate flags query to loader
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- mFlags = getDocumentFlags(context, uri);
+ final Uri docsUri = getArguments().getParcelable(EXTRA_DOCS_URI);
+ mFlags = getDocumentFlags(context, docsUri);
mCallbacks = new LoaderCallbacks<Cursor>() {
@Override
@@ -133,10 +137,10 @@ public class DirectoryFragment extends Fragment {
}
final Uri contentsUri;
- if (uri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
- contentsUri = uri;
+ if (docsUri.getQueryParameter(DocumentsContract.PARAM_QUERY) != null) {
+ contentsUri = docsUri;
} else {
- contentsUri = DocumentsContract.buildContentsUri(uri);
+ contentsUri = DocumentsContract.buildContentsUri(docsUri);
}
return new CursorLoader(context, contentsUri, null, null, null, sortOrder);
@@ -162,8 +166,8 @@ public class DirectoryFragment extends Fragment {
getLoaderManager().restartLoader(LOADER_DOCUMENTS, getArguments(), mCallbacks);
// TODO: clean up tracking of current directory
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- ((DocumentsActivity) getActivity()).onDirectoryChanged(uri, mFlags);
+ final Uri docsUri = getArguments().getParcelable(EXTRA_DOCS_URI);
+ ((DocumentsActivity) getActivity()).onDirectoryChanged(docsUri, mFlags);
}
@Override
@@ -245,8 +249,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 uri = getArguments().getParcelable(EXTRA_URI);
- final Document doc = Document.fromCursor(uri.getAuthority(), cursor);
+ final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI);
+ final Document doc = Document.fromCursor(rootUri, cursor);
((DocumentsActivity) getActivity()).onDocumentPicked(doc);
}
};
@@ -266,7 +270,7 @@ public class DirectoryFragment extends Fragment {
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (item.getItemId() == R.id.menu_open) {
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
+ final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI);
final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions();
final ArrayList<Document> docs = Lists.newArrayList();
@@ -274,7 +278,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(uri.getAuthority(), cursor));
+ docs.add(Document.fromCursor(rootUri, cursor));
}
}
@@ -336,17 +340,17 @@ public class DirectoryFragment extends Fragment {
final TextView summary = (TextView) view.findViewById(android.R.id.summary);
final ImageView icon = (ImageView) view.findViewById(android.R.id.icon);
- final String guid = getCursorString(cursor, DocumentColumns.GUID);
+ final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED);
final int flags = getCursorInt(cursor, DocumentColumns.FLAGS);
- final Uri uri = getArguments().getParcelable(EXTRA_URI);
- final String authority = uri.getAuthority();
+ final Uri rootUri = getArguments().getParcelable(EXTRA_ROOT_URI);
+ final String authority = rootUri.getAuthority();
if ((flags & DocumentsContract.FLAG_SUPPORTS_THUMBNAIL) != 0) {
- final Uri childUri = DocumentsContract.buildDocumentUri(authority, guid);
+ final Uri childUri = DocumentsContract.buildDocumentUri(rootUri, docId);
icon.setImageURI(childUri);
} else {
icon.setImageDrawable(
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
index dcd02d2..405ef36 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java
@@ -91,7 +91,6 @@ public class DocumentsActivity extends Activity {
private static final String TAG = "Documents";
// TODO: fragment to show recently opened documents
- // TODO: pull actionbar icon from current backend
private static final String TAG_CREATE_DIRECTORY = "create_directory";
@@ -250,7 +249,8 @@ public class DocumentsActivity extends Activity {
public boolean onQueryTextSubmit(String query) {
// TODO: clear existing directory stack?
final Uri searchUri = DocumentsContract.buildSearchUri(mCurrentDir, query);
- DirectoryFragment.show(getFragmentManager(), searchUri, query, true);
+ DirectoryFragment.show(
+ getFragmentManager(), mCurrentRoot.rootUri, searchUri, query, true);
mSearchView.setIconified(true);
return true;
}
@@ -398,7 +398,7 @@ public class DocumentsActivity extends Activity {
final FragmentManager fm = getFragmentManager();
if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(doc.mimeType)) {
// Nested directory picked, recurse using new fragment
- DirectoryFragment.show(fm, doc.uri, doc.displayName, true);
+ DirectoryFragment.show(fm, doc.rootUri, doc.uri, doc.displayName, true);
} else if (mAction == ACTION_OPEN) {
// Explicit file picked, return
onFinished(doc.uri);
@@ -418,6 +418,8 @@ public class DocumentsActivity extends Activity {
}
public void onSaveRequested(String mimeType, String displayName) {
+ // TODO: handle overwrite by using last-selected GUID
+
final ContentValues values = new ContentValues();
values.put(DocumentColumns.MIME_TYPE, mimeType);
values.put(DocumentColumns.DISPLAY_NAME, displayName);
@@ -426,7 +428,6 @@ public class DocumentsActivity extends Activity {
if (uri != null) {
onFinished(uri);
} else {
- // TODO: ask for overwrite confirmation
Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show();
}
}
@@ -470,35 +471,29 @@ public class DocumentsActivity extends Activity {
public static class Root {
public DocumentsProviderInfo info;
public int rootType;
+ public Uri rootUri;
public Uri uri;
public Drawable icon;
public String title;
public String summary;
- public static Root fromInfo(Context context, DocumentsProviderInfo info) {
+ 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();
root.info = info;
- root.rootType = DocumentsContract.ROOT_TYPE_SERVICE;
+ root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
+ root.rootUri = DocumentsContract.buildRootUri(info.providerInfo.authority, rootId);
root.uri = DocumentsContract.buildDocumentUri(
- info.providerInfo.authority, DocumentsContract.ROOT_GUID);
+ root.rootUri, DocumentsContract.ROOT_DOC_ID);
root.icon = info.providerInfo.loadIcon(pm);
root.title = info.providerInfo.loadLabel(pm).toString();
root.summary = null;
- return root;
- }
-
- public static Root fromCursor(
- Context context, DocumentsProviderInfo info, Cursor cursor) {
- final Root root = fromInfo(context, info);
-
- root.rootType = cursor.getInt(cursor.getColumnIndex(RootColumns.ROOT_TYPE));
- root.uri = DocumentsContract.buildDocumentUri(info.providerInfo.authority,
- cursor.getString(cursor.getColumnIndex(RootColumns.GUID)));
- final PackageManager pm = context.getPackageManager();
final int icon = cursor.getInt(cursor.getColumnIndex(RootColumns.ICON));
if (icon != 0) {
try {
@@ -534,21 +529,24 @@ public class DocumentsActivity extends Activity {
}
public static class Document {
+ public Uri rootUri;
public Uri uri;
public String mimeType;
public String displayName;
- public static Document fromCursor(String authority, Cursor cursor) {
+ public static Document fromCursor(Uri rootUri, Cursor cursor) {
final Document doc = new Document();
- final String guid = getCursorString(cursor, DocumentColumns.GUID);
- doc.uri = DocumentsContract.buildDocumentUri(authority, guid);
+ final String docId = getCursorString(cursor, DocumentColumns.DOC_ID);
+ doc.rootUri = rootUri;
+ doc.uri = DocumentsContract.buildDocumentUri(rootUri, docId);
doc.mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE);
doc.displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME);
return doc;
}
- public static Document fromUri(ContentResolver resolver, Uri uri) {
+ public static Document fromUri(ContentResolver resolver, Uri rootUri, Uri uri) {
final Document doc = new Document();
+ doc.rootUri = rootUri;
doc.uri = uri;
final Cursor cursor = resolver.query(uri, null, null, null, null);
@@ -639,19 +637,16 @@ public class DocumentsActivity extends Activity {
sProviders.put(info.providerInfo.authority, info);
- if (info.customRoots) {
- // TODO: populate roots on background thread, and cache results
- final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority);
- final Cursor cursor = getContentResolver().query(uri, null, null, null, null);
- try {
- while (cursor.moveToNext()) {
- sRoots.add(Root.fromCursor(this, info, cursor));
- }
- } finally {
- cursor.close();
+ // TODO: remove deprecated customRoots flag
+ // TODO: populate roots on background thread, and cache results
+ final Uri uri = DocumentsContract.buildRootsUri(providerInfo.authority);
+ final Cursor cursor = getContentResolver().query(uri, null, null, null, null);
+ try {
+ while (cursor.moveToNext()) {
+ sRoots.add(Root.fromCursor(this, info, cursor));
}
- } else if (info != null) {
- sRoots.add(Root.fromInfo(this, info));
+ } finally {
+ cursor.close();
}
}
}
@@ -721,8 +716,8 @@ public class DocumentsActivity extends Activity {
}
mCurrentRoot = mRootsAdapter.getItem(position);
- DirectoryFragment.show(
- getFragmentManager(), mCurrentRoot.uri, mCurrentRoot.title, false);
+ DirectoryFragment.show(getFragmentManager(), mCurrentRoot.rootUri, mCurrentRoot.uri,
+ mCurrentRoot.title, false);
mDrawerLayout.closeDrawers();
}
@@ -784,13 +779,16 @@ public class DocumentsActivity extends Activity {
values.put(DocumentColumns.MIME_TYPE, DocumentsContract.MIME_TYPE_DIRECTORY);
values.put(DocumentColumns.DISPLAY_NAME, displayName);
- // TODO: handle errors from remote side
final DocumentsActivity activity = (DocumentsActivity) getActivity();
final Uri uri = resolver.insert(activity.mCurrentDir, values);
-
- // Navigate into newly created child
- final Document doc = Document.fromUri(resolver, uri);
- activity.onDocumentPicked(doc);
+ if (uri != null) {
+ // Navigate into newly created child
+ final Document doc = Document.fromUri(
+ resolver, activity.mCurrentRoot.rootUri, uri);
+ activity.onDocumentPicked(doc);
+ } else {
+ Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show();
+ }
}
});
builder.setNegativeButton(android.R.string.cancel, null);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
new file mode 100644
index 0000000..e6ee614
--- /dev/null
+++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java
@@ -0,0 +1,177 @@
+/*
+ * 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 android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.UriMatcher;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+import android.net.Uri;
+import android.text.format.DateUtils;
+import android.util.Log;
+
+public class RecentsProvider extends ContentProvider {
+ private static final String TAG = "RecentsProvider";
+
+ public 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_RESUME = 3;
+
+ static {
+ sMatcher.addURI(AUTHORITY, "recent_open", URI_RECENT_OPEN);
+ sMatcher.addURI(AUTHORITY, "recent_create", URI_RECENT_CREATE);
+ sMatcher.addURI(AUTHORITY, "resume/*", URI_RESUME);
+ }
+
+ private static final String TABLE_RECENT_OPEN = "recent_open";
+ private static final String TABLE_RECENT_CREATE = "recent_create";
+ private static final String TABLE_RESUME = "resume";
+
+ /**
+ * String of URIs pointing at a storage backend, stored as a JSON array,
+ * starting with root.
+ */
+ public static final String COL_PATH = "path";
+ public static final String COL_PACKAGE_NAME = "package_name";
+ public static final String COL_TIMESTAMP = "timestamp";
+
+ private DatabaseHelper mHelper;
+
+ private static class DatabaseHelper extends SQLiteOpenHelper {
+ private static final String DB_NAME = "recents";
+
+ private static final int VERSION_INIT = 1;
+
+ public DatabaseHelper(Context context) {
+ super(context, DB_NAME, null, VERSION_INIT);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ db.execSQL("CREATE TABLE " + TABLE_RECENT_OPEN + " (" +
+ COL_PATH + " TEXT," +
+ COL_TIMESTAMP + " INTEGER," +
+ ")");
+
+ db.execSQL("CREATE TABLE " + TABLE_RECENT_CREATE + " (" +
+ COL_PATH + " TEXT," +
+ COL_TIMESTAMP + " INTEGER," +
+ ")");
+
+ db.execSQL("CREATE TABLE " + TABLE_RESUME + " (" +
+ COL_PACKAGE_NAME + " TEXT PRIMARY KEY ON CONFLICT REPLACE," +
+ COL_PATH + " TEXT," +
+ COL_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_RESUME);
+ onCreate(db);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ mHelper = new DatabaseHelper(getContext());
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ 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: {
+ final String packageName = uri.getPathSegments().get(1);
+ return db.query(TABLE_RESUME, projection, COL_PACKAGE_NAME + "=?",
+ new String[] { packageName }, null, null, null);
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ final SQLiteDatabase db = mHelper.getWritableDatabase();
+ switch (sMatcher.match(uri)) {
+ case URI_RECENT_OPEN: {
+ db.insert(TABLE_RECENT_OPEN, null, values);
+ db.delete(TABLE_RECENT_OPEN, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null);
+ return uri;
+ }
+ case URI_RECENT_CREATE: {
+ db.insert(TABLE_RECENT_CREATE, null, values);
+ db.delete(TABLE_RECENT_CREATE, buildWhereOlder(DateUtils.WEEK_IN_MILLIS), null);
+ return uri;
+ }
+ case URI_RESUME: {
+ final String packageName = uri.getPathSegments().get(1);
+ values.put(COL_PACKAGE_NAME, packageName);
+ db.insert(TABLE_RESUME, null, values);
+ return uri;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+
+ private static String buildWhereOlder(long deltaMillis) {
+ return COL_TIMESTAMP + "<" + (System.currentTimeMillis() - deltaMillis);
+ }
+
+ private static String buildWhereYounger(long deltaMillis) {
+ return COL_TIMESTAMP + ">" + (System.currentTimeMillis() - deltaMillis);
+ }
+}
diff --git a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
index b15d123..a086a43 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/TestActivity.java
@@ -19,7 +19,9 @@ package com.android.documentsui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.net.Uri;
import android.os.Bundle;
+import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -27,7 +29,15 @@ import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
+import libcore.io.IoUtils;
+import libcore.io.Streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+
public class TestActivity extends Activity {
+ private static final String TAG = "TestActivity";
+
private TextView mResult;
@Override
@@ -113,5 +123,19 @@ public class TestActivity extends Activity {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mResult.setText("resultCode=" + resultCode + ", data=" + String.valueOf(data));
+
+ final Uri uri = data != null ? data.getData() : null;
+ if (uri != null) {
+ InputStream is = null;
+ try {
+ is = getContentResolver().openInputStream(uri);
+ final int length = Streams.readFullyNoClose(is).length;
+ Log.d(TAG, "read length=" + length);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to read " + uri, e);
+ } finally {
+ IoUtils.closeQuietly(is);
+ }
+ }
}
}