summaryrefslogtreecommitdiffstats
path: root/packages/ExternalStorageProvider
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-08-27 18:26:48 -0700
committerJeff Sharkey <jsharkey@android.com>2013-08-28 20:49:42 -0700
commitaeb16e2435f9975b9fa1fc4b747796647a21292e (patch)
tree2e228b0ca25799bf59472d5df08d71e068498b2b /packages/ExternalStorageProvider
parent61f0f9ada51a6718075acfe7fafbb9954f38e980 (diff)
downloadframeworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.zip
frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.tar.gz
frameworks_base-aeb16e2435f9975b9fa1fc4b747796647a21292e.tar.bz2
Stronger DocumentsProvider contract.
Using a contract class requires that a provider implement it exactly with little help. This change introduces a DocumentsProvider abstract class that provides a client-side implementation of the contract that greatly reduces developer burden, and improves correctness. This also moves to first-class DocumentRoot objects, and moves calls with complex side effects to be ContentProvider.call() invocations, offering more granular permission control over Uri operations that shouldn't be available through Uri grants. This new design also relaxes the requirement that root information be burned into every Uri. Migrate ExternalDocumentsProvider and DocumentsUI to adopt new API. Bug: 10497206 Change-Id: I6f2b3f519bfd62a9d693223ea5628a971ce2e743
Diffstat (limited to 'packages/ExternalStorageProvider')
-rw-r--r--packages/ExternalStorageProvider/AndroidManifest.xml13
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/CloudTestDocumentsProvider.java253
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java473
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 {