diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-08-02 10:33:21 -0700 |
---|---|---|
committer | Jeff Sharkey <jsharkey@android.com> | 2013-08-02 11:05:11 -0700 |
commit | 92d7e697a864a3e18bef4ef256bb3eb339a66b4e (patch) | |
tree | e5cce35e071787bfb81a8341716071925f1c86c4 /packages/DocumentsUI/src/com | |
parent | 7e258b31e70464bb6d80b8b42f0cef8e4417bd6a (diff) | |
download | frameworks_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')
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); + } + } } } |