diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-08-29 04:27:02 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-08-29 04:27:02 +0000 |
commit | 9fd81a9008d5c8dd33272b6a451d89fa2fa1841e (patch) | |
tree | 972eb1e03b7baf79f25363cb55954aaa1e216eea /packages/ExternalStorageProvider | |
parent | 166853e6c90abe84c556a574589419f576c5e986 (diff) | |
parent | aeb16e2435f9975b9fa1fc4b747796647a21292e (diff) | |
download | frameworks_base-9fd81a9008d5c8dd33272b6a451d89fa2fa1841e.zip frameworks_base-9fd81a9008d5c8dd33272b6a451d89fa2fa1841e.tar.gz frameworks_base-9fd81a9008d5c8dd33272b6a451d89fa2fa1841e.tar.bz2 |
Merge "Stronger DocumentsProvider contract." into klp-dev
Diffstat (limited to 'packages/ExternalStorageProvider')
3 files changed, 186 insertions, 553 deletions
diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 8bd2a6d..5272166 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -15,18 +15,5 @@ android:name="android.content.DOCUMENT_PROVIDER" android:resource="@xml/document_provider" /> </provider> - - <!-- TODO: remove when we have real providers --> - <provider - android:name=".CloudTestDocumentsProvider" - android:authorities="com.android.externalstorage.cloudtest" - android:grantUriPermissions="true" - android:exported="true" - android:enabled="false" - android:permission="android.permission.MANAGE_DOCUMENTS"> - <meta-data - android:name="android.content.DOCUMENT_PROVIDER" - android:resource="@xml/document_provider" /> - </provider> </application> </manifest> diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java deleted file mode 100644 index 119d92e..0000000 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * 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.externalstorage; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.UriMatcher; -import android.database.Cursor; -import android.database.MatrixCursor; -import android.database.MatrixCursor.RowBuilder; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.ParcelFileDescriptor; -import android.os.SystemClock; -import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.Documents; -import android.provider.DocumentsContract.RootColumns; -import android.provider.DocumentsContract.Roots; -import android.util.Log; - -import com.google.android.collect.Lists; - -import libcore.io.IoUtils; - -import java.io.FileNotFoundException; -import java.util.List; - -public class CloudTestDocumentsProvider extends ContentProvider { - private static final String TAG = "CloudTest"; - - private static final String AUTHORITY = "com.android.externalstorage.cloudtest"; - - private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - private static final int URI_ROOTS = 1; - private static final int URI_ROOTS_ID = 2; - private static final int URI_DOCS_ID = 3; - private static final int URI_DOCS_ID_CONTENTS = 4; - private static final int URI_DOCS_ID_SEARCH = 5; - - static { - sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS); - sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH); - } - - private static final String[] ALL_ROOTS_COLUMNS = new String[] { - RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE, - RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES - }; - - private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] { - DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, - DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS - }; - - private List<String> mKnownDocs = Lists.newArrayList("meow.png", "kittens.pdf"); - - private int mPage; - - @Override - public boolean onCreate() { - return true; - } - - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - switch (sMatcher.match(uri)) { - case URI_ROOTS: { - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_ROOTS_COLUMNS); - includeDefaultRoot(result); - return result; - } - case URI_ROOTS_ID: { - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_ROOTS_COLUMNS); - includeDefaultRoot(result); - return result; - } - case URI_DOCS_ID: { - final String docId = DocumentsContract.getDocId(uri); - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_DOCUMENTS_COLUMNS); - includeDoc(result, docId); - return result; - } - case URI_DOCS_ID_CONTENTS: { - final CloudCursor result = new CloudCursor( - projection != null ? projection : ALL_DOCUMENTS_COLUMNS, uri); - for (String docId : mKnownDocs) { - includeDoc(result, docId); - } - if (mPage < 3) { - result.setHasMore(); - } - result.setNotificationUri(getContext().getContentResolver(), uri); - return result; - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } - } - } - - private void includeDefaultRoot(MatrixCursor result) { - final RowBuilder row = result.newRow(); - row.offer(RootColumns.ROOT_ID, "testroot"); - row.offer(RootColumns.ROOT_TYPE, Roots.ROOT_TYPE_SERVICE); - row.offer(RootColumns.TITLE, "_TestTitle"); - row.offer(RootColumns.SUMMARY, "_TestSummary"); - } - - private void includeDoc(MatrixCursor result, String docId) { - int flags = 0; - - final String mimeType; - if (Documents.DOC_ID_ROOT.equals(docId)) { - mimeType = Documents.MIME_TYPE_DIR; - } else { - mimeType = "application/octet-stream"; - } - - final RowBuilder row = result.newRow(); - row.offer(DocumentColumns.DOC_ID, docId); - row.offer(DocumentColumns.DISPLAY_NAME, docId); - row.offer(DocumentColumns.MIME_TYPE, mimeType); - row.offer(DocumentColumns.LAST_MODIFIED, System.currentTimeMillis()); - row.offer(DocumentColumns.FLAGS, flags); - } - - private class CloudCursor extends MatrixCursor { - private final Uri mUri; - private Bundle mExtras = new Bundle(); - - public CloudCursor(String[] columnNames, Uri uri) { - super(columnNames); - mUri = uri; - } - - public void setHasMore() { - mExtras.putBoolean(DocumentsContract.EXTRA_HAS_MORE, true); - } - - @Override - public Bundle getExtras() { - Log.d(TAG, "getExtras() " + mExtras); - return mExtras; - } - - @Override - public Bundle respond(Bundle extras) { - extras.size(); - Log.d(TAG, "respond() " + extras); - if (extras.getBoolean(DocumentsContract.EXTRA_REQUEST_MORE, false)) { - new CloudTask().execute(mUri); - } - return Bundle.EMPTY; - } - } - - private class CloudTask extends AsyncTask<Uri, Void, Void> { - @Override - protected Void doInBackground(Uri... uris) { - final Uri uri = uris[0]; - - SystemClock.sleep(1000); - - // Grab some files from the cloud - for (int i = 0; i < 5; i++) { - mKnownDocs.add("cloud-page" + mPage + "-file" + i); - } - mPage++; - - Log.d(TAG, "Loaded more; notifying " + uri); - getContext().getContentResolver().notifyChange(uri, null, false); - return null; - } - } - - private interface TypeQuery { - final String[] PROJECTION = { - DocumentColumns.MIME_TYPE }; - - final int MIME_TYPE = 0; - } - - @Override - public String getType(Uri uri) { - switch (sMatcher.match(uri)) { - case URI_ROOTS: { - return Roots.MIME_TYPE_DIR; - } - case URI_ROOTS_ID: { - return Roots.MIME_TYPE_ITEM; - } - case URI_DOCS_ID: { - final Cursor cursor = query(uri, TypeQuery.PROJECTION, null, null, null); - try { - if (cursor.moveToFirst()) { - return cursor.getString(TypeQuery.MIME_TYPE); - } else { - return null; - } - } finally { - IoUtils.closeQuietly(cursor); - } - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } - } - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } - - @Override - public Uri insert(Uri uri, ContentValues values) { - 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); - } -} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 8843e19..583ecc9 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,205 +16,130 @@ package com.android.externalstorage; -import android.content.ContentProvider; import android.content.ContentResolver; -import android.content.ContentValues; -import android.content.UriMatcher; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; +import android.graphics.Point; import android.media.ExifInterface; -import android.net.Uri; -import android.os.Bundle; +import android.os.CancellationSignal; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract; import android.provider.DocumentsContract.DocumentColumns; +import android.provider.DocumentsContract.DocumentRoot; import android.provider.DocumentsContract.Documents; -import android.provider.DocumentsContract.RootColumns; -import android.provider.DocumentsContract.Roots; -import android.util.Log; +import android.provider.DocumentsProvider; import android.webkit.MimeTypeMap; +import com.google.android.collect.Lists; import com.google.android.collect.Maps; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; +import java.util.List; +import java.util.Map; -public class ExternalStorageProvider extends ContentProvider { +public class ExternalStorageProvider extends DocumentsProvider { private static final String TAG = "ExternalStorage"; - private static final String AUTHORITY = "com.android.externalstorage.documents"; + // docId format: root:path/to/file - // TODO: support multiple storage devices - - private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - private static final int URI_ROOTS = 1; - private static final int URI_ROOTS_ID = 2; - private static final int URI_DOCS_ID = 3; - private static final int URI_DOCS_ID_CONTENTS = 4; - private static final int URI_DOCS_ID_SEARCH = 5; - - static { - sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS); - sMatcher.addURI(AUTHORITY, "roots/*", URI_ROOTS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*", URI_DOCS_ID); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/contents", URI_DOCS_ID_CONTENTS); - sMatcher.addURI(AUTHORITY, "roots/*/docs/*/search", URI_DOCS_ID_SEARCH); - } - - private HashMap<String, Root> mRoots = Maps.newHashMap(); - - private static class Root { - public int rootType; - public String name; - public int icon = 0; - public String title = null; - public String summary = null; - public File path; - } - - private static final String[] ALL_ROOTS_COLUMNS = new String[] { - RootColumns.ROOT_ID, RootColumns.ROOT_TYPE, RootColumns.ICON, RootColumns.TITLE, - RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES - }; - - private static final String[] ALL_DOCUMENTS_COLUMNS = new String[] { + private static final String[] SUPPORTED_COLUMNS = new String[] { DocumentColumns.DOC_ID, DocumentColumns.DISPLAY_NAME, DocumentColumns.SIZE, DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS }; + private ArrayList<DocumentRoot> mRoots; + private HashMap<String, DocumentRoot> mTagToRoot; + private HashMap<String, File> mTagToPath; + @Override public boolean onCreate() { - mRoots.clear(); - - final Root root = new Root(); - root.rootType = Roots.ROOT_TYPE_DEVICE_ADVANCED; - root.name = "primary"; - root.title = getContext().getString(R.string.root_internal_storage); - root.path = Environment.getExternalStorageDirectory(); - mRoots.put(root.name, root); + mRoots = Lists.newArrayList(); + mTagToRoot = Maps.newHashMap(); + mTagToPath = Maps.newHashMap(); + + // TODO: support multiple storage devices + + try { + final String tag = "primary"; + final File path = Environment.getExternalStorageDirectory(); + mTagToPath.put(tag, path); + + final DocumentRoot root = new DocumentRoot(); + root.docId = getDocIdForFile(path); + root.rootType = DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED; + root.title = getContext().getString(R.string.root_internal_storage); + root.icon = R.drawable.ic_pdf; + root.flags = DocumentRoot.FLAG_LOCAL_ONLY; + mRoots.add(root); + mTagToRoot.put(tag, root); + } catch (FileNotFoundException e) { + throw new IllegalStateException(e); + } return true; } - @Override - public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { - switch (sMatcher.match(uri)) { - case URI_ROOTS: { - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_ROOTS_COLUMNS); - for (Root root : mRoots.values()) { - includeRoot(result, root); - } - return result; - } - case URI_ROOTS_ID: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); + private String getDocIdForFile(File file) throws FileNotFoundException { + String path = file.getAbsolutePath(); - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_ROOTS_COLUMNS); - includeRoot(result, root); - return result; + // Find the most-specific root path + Map.Entry<String, File> mostSpecific = null; + for (Map.Entry<String, File> root : mTagToPath.entrySet()) { + final String rootPath = root.getValue().getPath(); + if (path.startsWith(rootPath) && (mostSpecific == null + || rootPath.length() > mostSpecific.getValue().getPath().length())) { + mostSpecific = root; } - case URI_DOCS_ID: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_DOCUMENTS_COLUMNS); - final File file = docIdToFile(root, docId); - includeFile(result, root, file); - return result; - } - case URI_DOCS_ID_CONTENTS: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_DOCUMENTS_COLUMNS); - final File parent = docIdToFile(root, docId); - - for (File file : parent.listFiles()) { - includeFile(result, root, file); - } + } - return result; - } - case URI_DOCS_ID_SEARCH: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - final String query = DocumentsContract.getSearchQuery(uri).toLowerCase(); - - final MatrixCursor result = new MatrixCursor( - projection != null ? projection : ALL_DOCUMENTS_COLUMNS); - final File parent = docIdToFile(root, docId); - - final LinkedList<File> pending = new LinkedList<File>(); - pending.add(parent); - while (!pending.isEmpty() && result.getCount() < 20) { - final File file = pending.removeFirst(); - if (file.isDirectory()) { - for (File child : file.listFiles()) { - pending.add(child); - } - } else { - if (file.getName().toLowerCase().contains(query)) { - includeFile(result, root, file); - } - } - } + if (mostSpecific == null) { + throw new FileNotFoundException("Failed to find root that contains " + path); + } - return result; - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } + // Start at first char of path under root + final String rootPath = mostSpecific.getValue().getPath(); + if (rootPath.equals(path)) { + path = ""; + } else if (rootPath.endsWith("/")) { + path = path.substring(rootPath.length()); + } else { + path = path.substring(rootPath.length() + 1); } + + return mostSpecific.getKey() + ':' + path; } - private String fileToDocId(Root root, File file) { - String rootPath = root.path.getAbsolutePath(); - final String path = file.getAbsolutePath(); - if (path.equals(rootPath)) { - return Documents.DOC_ID_ROOT; - } + private File getFileForDocId(String docId) throws FileNotFoundException { + final int splitIndex = docId.indexOf(':', 1); + final String tag = docId.substring(0, splitIndex); + final String path = docId.substring(splitIndex + 1); - if (!rootPath.endsWith("/")) { - rootPath += "/"; + File target = mTagToPath.get(tag); + if (target == null) { + throw new FileNotFoundException("No root for " + tag); } - if (!path.startsWith(rootPath)) { - throw new IllegalArgumentException("File " + path + " outside root " + root.path); - } else { - return path.substring(rootPath.length()); + target = new File(target, path); + if (!target.exists()) { + throw new FileNotFoundException("Missing file for " + docId + " at " + target); } + return target; } - private File docIdToFile(Root root, String docId) { - if (Documents.DOC_ID_ROOT.equals(docId)) { - return root.path; + private void includeFile(MatrixCursor result, String docId, File file) + throws FileNotFoundException { + if (docId == null) { + docId = getDocIdForFile(file); } else { - return new File(root.path, docId); + file = getFileForDocId(docId); } - } - private void includeRoot(MatrixCursor result, Root root) { - final RowBuilder row = result.newRow(); - row.offer(RootColumns.ROOT_ID, root.name); - row.offer(RootColumns.ROOT_TYPE, root.rootType); - row.offer(RootColumns.ICON, root.icon); - row.offer(RootColumns.TITLE, root.title); - row.offer(RootColumns.SUMMARY, root.summary); - row.offer(RootColumns.AVAILABLE_BYTES, root.path.getFreeSpace()); - } - - private void includeFile(MatrixCursor result, Root root, File file) { int flags = 0; if (file.isDirectory()) { @@ -229,19 +154,12 @@ public class ExternalStorageProvider extends ContentProvider { flags |= Documents.FLAG_SUPPORTS_DELETE; } + final String displayName = file.getName(); final String mimeType = getTypeForFile(file); if (mimeType.startsWith("image/")) { flags |= Documents.FLAG_SUPPORTS_THUMBNAIL; } - final String docId = fileToDocId(root, file); - final String displayName; - if (Documents.DOC_ID_ROOT.equals(docId)) { - displayName = root.title; - } else { - displayName = file.getName(); - } - final RowBuilder row = result.newRow(); row.offer(DocumentColumns.DOC_ID, docId); row.offer(DocumentColumns.DISPLAY_NAME, displayName); @@ -252,169 +170,150 @@ public class ExternalStorageProvider extends ContentProvider { } @Override - public String getType(Uri uri) { - switch (sMatcher.match(uri)) { - case URI_ROOTS: { - return Roots.MIME_TYPE_DIR; - } - case URI_ROOTS_ID: { - return Roots.MIME_TYPE_ITEM; - } - case URI_DOCS_ID: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - return getTypeForFile(docIdToFile(root, docId)); - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } + public List<DocumentRoot> getDocumentRoots() { + // Update free space + for (String tag : mTagToRoot.keySet()) { + final DocumentRoot root = mTagToRoot.get(tag); + final File path = mTagToPath.get(tag); + root.availableBytes = path.getFreeSpace(); } + return mRoots; } - private String getTypeForFile(File file) { - if (file.isDirectory()) { - return Documents.MIME_TYPE_DIR; + @Override + public String createDocument(String docId, String mimeType, String displayName) + throws FileNotFoundException { + final File parent = getFileForDocId(docId); + displayName = validateDisplayName(mimeType, displayName); + + final File file = new File(parent, displayName); + if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (!file.mkdir()) { + throw new IllegalStateException("Failed to mkdir " + file); + } } else { - return getTypeForName(file.getName()); + try { + if (!file.createNewFile()) { + throw new IllegalStateException("Failed to touch " + file); + } + } catch (IOException e) { + throw new IllegalStateException("Failed to touch " + file + ": " + e); + } } + return getDocIdForFile(file); } - private String getTypeForName(String name) { - final int lastDot = name.lastIndexOf('.'); - if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1); - final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - if (mime != null) { - return mime; - } + @Override + public void renameDocument(String docId, String displayName) throws FileNotFoundException { + final File file = getFileForDocId(docId); + final File newFile = new File(file.getParentFile(), displayName); + if (!file.renameTo(newFile)) { + throw new IllegalStateException("Failed to rename " + docId); } - - return "application/octet-stream"; + // TODO: update any outstanding grants } @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - switch (sMatcher.match(uri)) { - case URI_DOCS_ID: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - - final File file = docIdToFile(root, docId); - return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode)); - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } + public void deleteDocument(String docId) throws FileNotFoundException { + final File file = getFileForDocId(docId); + if (!file.delete()) { + throw new IllegalStateException("Failed to delete " + file); } } @Override - public AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) - throws FileNotFoundException { - if (opts == null || !opts.containsKey(DocumentsContract.EXTRA_THUMBNAIL_SIZE)) { - return super.openTypedAssetFile(uri, mimeTypeFilter, opts); + public Cursor queryDocument(String docId) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); + includeFile(result, docId, null); + return result; + } + + @Override + public Cursor queryDocumentChildren(String docId) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); + final File parent = getFileForDocId(docId); + for (File file : parent.listFiles()) { + includeFile(result, null, file); } + return result; + } - switch (sMatcher.match(uri)) { - case URI_DOCS_ID: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - - final File file = docIdToFile(root, docId); - final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( - file, ParcelFileDescriptor.MODE_READ_ONLY); - - try { - final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); - final long[] thumb = exif.getThumbnailRange(); - if (thumb != null) { - return new AssetFileDescriptor(pfd, thumb[0], thumb[1]); - } - } catch (IOException e) { + @Override + public Cursor querySearch(String docId, String query) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); + final File parent = getFileForDocId(docId); + + final LinkedList<File> pending = new LinkedList<File>(); + pending.add(parent); + while (!pending.isEmpty() && result.getCount() < 20) { + final File file = pending.removeFirst(); + if (file.isDirectory()) { + for (File child : file.listFiles()) { + pending.add(child); + } + } else { + if (file.getName().toLowerCase().contains(query)) { + includeFile(result, null, file); } - - return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); } } + return result; } @Override - public Uri insert(Uri uri, ContentValues values) { - switch (sMatcher.match(uri)) { - case URI_DOCS_ID: { - final Root root = mRoots.get(DocumentsContract.getRootId(uri)); - final String docId = DocumentsContract.getDocId(uri); - - final File parent = docIdToFile(root, docId); - - final String mimeType = values.getAsString(DocumentColumns.MIME_TYPE); - final String name = validateDisplayName( - values.getAsString(DocumentColumns.DISPLAY_NAME), mimeType); - - final File file = new File(parent, name); - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { - if (!file.mkdir()) { - return null; - } - - } else { - try { - if (!file.createNewFile()) { - return null; - } - } catch (IOException e) { - Log.w(TAG, "Failed to create file", e); - return null; - } - } + public String getType(String docId) throws FileNotFoundException { + final File file = getFileForDocId(docId); + return getTypeForFile(file); + } - final String newDocId = fileToDocId(root, file); - return DocumentsContract.buildDocumentUri(AUTHORITY, root.name, newDocId); - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); - } - } + @Override + public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) + throws FileNotFoundException { + final File file = getFileForDocId(docId); + return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode)); } @Override - public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - switch (sMatcher.match(uri)) { - case URI_DOCS_ID: { - 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( - file.getParentFile(), values.getAsString(DocumentColumns.DISPLAY_NAME)); - return file.renameTo(newFile) ? 1 : 0; - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); + public AssetFileDescriptor openDocumentThumbnail( + String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { + final File file = getFileForDocId(docId); + final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( + file, ParcelFileDescriptor.MODE_READ_ONLY); + + try { + final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); + final long[] thumb = exif.getThumbnailRange(); + if (thumb != null) { + return new AssetFileDescriptor(pfd, thumb[0], thumb[1]); } + } catch (IOException e) { } + + return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH); } - @Override - public int delete(Uri uri, String selection, String[] selectionArgs) { - switch (sMatcher.match(uri)) { - case URI_DOCS_ID: { - 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; - } - default: { - throw new UnsupportedOperationException("Unsupported Uri " + uri); + private static String getTypeForFile(File file) { + if (file.isDirectory()) { + return Documents.MIME_TYPE_DIR; + } else { + return getTypeForName(file.getName()); + } + } + + private static String getTypeForName(String name) { + final int lastDot = name.lastIndexOf('.'); + if (lastDot >= 0) { + final String extension = name.substring(lastDot + 1); + final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + if (mime != null) { + return mime; } } + + return "application/octet-stream"; } - private String validateDisplayName(String displayName, String mimeType) { + private static String validateDisplayName(String mimeType, String displayName) { if (Documents.MIME_TYPE_DIR.equals(mimeType)) { return displayName; } else { |