summaryrefslogtreecommitdiffstats
path: root/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2013-07-30 17:08:39 -0700
committerJeff Sharkey <jsharkey@android.com>2013-07-30 22:55:23 -0700
commit20d96d8aff2193d548977e23ce5158657cac94e0 (patch)
treeb650fadd3425d2b72a4ef6d9e0f180596b5b54f1 /packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
parent5259ffba255b38728a20e28aa6ba029416d0e925 (diff)
downloadframeworks_base-20d96d8aff2193d548977e23ce5158657cac94e0.zip
frameworks_base-20d96d8aff2193d548977e23ce5158657cac94e0.tar.gz
frameworks_base-20d96d8aff2193d548977e23ce5158657cac94e0.tar.bz2
Define storage roots, external GUIDs, creation.
Allow storage backends to publish multiple roots into the UI, which are defined by a directory GUID, type, and label details. Update external provider to surface a primary external storage root, and switch to burning file path into the returned GUIDs so they remain durable. Added insert, update, and delete support to external provider. Adds file extensions to display names when needed to match MIME type. Add flags for searching and deletion, and extras for Cursor pagination. Add directory creation dialog to UI. Opening a document always gives write access. Change-Id: I9bea1aa0dcde909a5ab86aefeece7451ab920cf1
Diffstat (limited to 'packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java')
-rw-r--r--packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java246
1 files changed, 192 insertions, 54 deletions
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index f75e3bd..bf5811b 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -28,14 +28,17 @@ import android.os.ParcelFileDescriptor;
import android.provider.BaseColumns;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.DocumentColumns;
+import android.provider.DocumentsContract.RootColumns;
+import android.util.Log;
import android.webkit.MimeTypeMap;
-import com.android.internal.annotations.GuardedBy;
-import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
import java.io.File;
import java.io.FileNotFoundException;
-import java.util.ArrayList;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.LinkedList;
public class ExternalStorageProvider extends ContentProvider {
private static final String TAG = "ExternalStorage";
@@ -44,33 +47,65 @@ public class ExternalStorageProvider extends ContentProvider {
// TODO: support searching
// TODO: support multiple storage devices
- // TODO: persist GUIDs across launches
private static final UriMatcher sMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- private static final int URI_DOCS_ID = 1;
- private static final int URI_DOCS_ID_CONTENTS = 2;
- private static final int URI_SEARCH = 3;
+ private static final int URI_ROOTS = 1;
+ private static final int URI_DOCS_ID = 2;
+ private static final int URI_DOCS_ID_CONTENTS = 3;
+ private static final int URI_DOCS_ID_SEARCH = 4;
- static {
- sMatcher.addURI(AUTHORITY, "docs/#", URI_DOCS_ID);
- sMatcher.addURI(AUTHORITY, "docs/#/contents", URI_DOCS_ID_CONTENTS);
- sMatcher.addURI(AUTHORITY, "search", URI_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;
}
- @GuardedBy("mFiles")
- private ArrayList<File> mFiles = Lists.newArrayList();
+ static {
+ sMatcher.addURI(AUTHORITY, "roots", URI_ROOTS);
+ sMatcher.addURI(AUTHORITY, "docs/*", URI_DOCS_ID);
+ sMatcher.addURI(AUTHORITY, "docs/*/contents", URI_DOCS_ID_CONTENTS);
+ sMatcher.addURI(AUTHORITY, "docs/*/search", URI_DOCS_ID_SEARCH);
+ }
@Override
public boolean onCreate() {
- mFiles.clear();
- mFiles.add(Environment.getExternalStorageDirectory());
+ mRoots.clear();
+
+ final Root root = new Root();
+ root.rootType = DocumentsContract.ROOT_TYPE_DEVICE_ADVANCED;
+ root.name = "internal";
+ root.title = getContext().getString(R.string.root_internal_storage);
+ root.path = Environment.getExternalStorageDirectory();
+ mRoots.put(root.name, root);
+
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
+ final int match = sMatcher.match(uri);
+ if (match == URI_ROOTS) {
+ // TODO: support custom projections
+ projection = new String[] {
+ RootColumns.ROOT_TYPE, RootColumns.GUID, RootColumns.ICON, RootColumns.TITLE,
+ RootColumns.SUMMARY, RootColumns.AVAILABLE_BYTES };
+
+ final MatrixCursor cursor = new MatrixCursor(projection);
+ for (Root root : mRoots.values()) {
+ final String guid = fileToGuid(root.path);
+ cursor.addRow(new Object[] {
+ root.rootType, guid, root.icon, root.title, root.summary,
+ root.path.getFreeSpace() });
+ }
+ return cursor;
+ }
// TODO: support custom projections
projection = new String[] {
@@ -79,21 +114,37 @@ public class ExternalStorageProvider extends ContentProvider {
DocumentColumns.MIME_TYPE, DocumentColumns.LAST_MODIFIED, DocumentColumns.FLAGS };
final MatrixCursor cursor = new MatrixCursor(projection);
- switch (sMatcher.match(uri)) {
+ switch (match) {
case URI_DOCS_ID: {
- final int id = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- includeFileLocked(cursor, id);
- }
+ final String guid = uri.getPathSegments().get(1);
+ includeFile(cursor, guid);
break;
}
case URI_DOCS_ID_CONTENTS: {
- final int parentId = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- final File parent = mFiles.get(parentId);
- for (File file : parent.listFiles()) {
- final int id = findOrCreateFileLocked(file);
- includeFileLocked(cursor, id);
+ final String guid = uri.getPathSegments().get(1);
+ final File parent = guidToFile(guid);
+ for (File file : parent.listFiles()) {
+ includeFile(cursor, fileToGuid(file));
+ }
+ break;
+ }
+ case URI_DOCS_ID_SEARCH: {
+ final String guid = uri.getPathSegments().get(1);
+ final File parent = guidToFile(guid);
+ final String query = uri.getQueryParameter(DocumentsContract.PARAM_QUERY).toLowerCase();
+
+ final LinkedList<File> pending = new LinkedList<File>();
+ pending.add(parent);
+ while (!pending.isEmpty() && cursor.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(cursor, fileToGuid(file));
+ }
}
}
break;
@@ -107,43 +158,61 @@ public class ExternalStorageProvider extends ContentProvider {
return cursor;
}
- private int findOrCreateFileLocked(File file) {
- int id = mFiles.indexOf(file);
- if (id == -1) {
- id = mFiles.size();
- mFiles.add(file);
+ private String fileToGuid(File file) {
+ final String path = file.getAbsolutePath();
+ for (Root root : mRoots.values()) {
+ final String rootPath = root.path.getAbsolutePath();
+ if (path.startsWith(rootPath)) {
+ return root.name + ':' + Uri.encode(path.substring(rootPath.length()));
+ }
}
- return id;
+
+ throw new IllegalArgumentException("Failed to find root for " + file);
+ }
+
+ private File guidToFile(String guid) {
+ final int split = guid.indexOf(':');
+ final String name = guid.substring(0, split);
+ final Root root = mRoots.get(name);
+ if (root != null) {
+ final String path = Uri.decode(guid.substring(split + 1));
+ return new File(root.path, path);
+ }
+
+ throw new IllegalArgumentException("Failed to find root for " + guid);
}
- private void includeFileLocked(MatrixCursor cursor, int id) {
- final File file = mFiles.get(id);
+ private void includeFile(MatrixCursor cursor, String guid) {
int flags = 0;
+ final File file = guidToFile(guid);
+ if (file.isDirectory()) {
+ flags |= DocumentsContract.FLAG_SUPPORTS_SEARCH;
+ }
if (file.isDirectory() && file.canWrite()) {
flags |= DocumentsContract.FLAG_SUPPORTS_CREATE;
}
if (file.canWrite()) {
flags |= DocumentsContract.FLAG_SUPPORTS_RENAME;
+ flags |= DocumentsContract.FLAG_SUPPORTS_DELETE;
}
- final String mimeType = getTypeLocked(id);
+ final String mimeType = getTypeForFile(file);
if (mimeType.startsWith("image/")) {
flags |= DocumentsContract.FLAG_SUPPORTS_THUMBNAIL;
}
+ final long id = guid.hashCode();
cursor.addRow(new Object[] {
- id, file.getName(), file.length(), id, mimeType, file.lastModified(), flags });
+ id, file.getName(), file.length(), guid, mimeType, file.lastModified(), flags });
}
@Override
public String getType(Uri uri) {
switch (sMatcher.match(uri)) {
case URI_DOCS_ID: {
- final int id = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- return getTypeLocked(id);
- }
+ final String guid = uri.getPathSegments().get(1);
+ return getTypeForFile(guidToFile(guid));
}
default: {
throw new UnsupportedOperationException("Unsupported Uri " + uri);
@@ -151,16 +220,18 @@ public class ExternalStorageProvider extends ContentProvider {
}
}
- private String getTypeLocked(int id) {
- final File file = mFiles.get(id);
-
+ private String getTypeForFile(File file) {
if (file.isDirectory()) {
return DocumentsContract.MIME_TYPE_DIRECTORY;
+ } else {
+ return getTypeForName(file.getName());
}
+ }
- final int lastDot = file.getName().lastIndexOf('.');
+ private String getTypeForName(String name) {
+ final int lastDot = name.lastIndexOf('.');
if (lastDot >= 0) {
- final String extension = file.getName().substring(lastDot + 1);
+ final String extension = name.substring(lastDot + 1);
final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
if (mime != null) {
return mime;
@@ -174,12 +245,11 @@ public class ExternalStorageProvider extends ContentProvider {
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
switch (sMatcher.match(uri)) {
case URI_DOCS_ID: {
- final int id = Integer.parseInt(uri.getPathSegments().get(1));
- synchronized (mFiles) {
- final File file = mFiles.get(id);
- // TODO: turn into thumbnail
- return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode));
- }
+ final String guid = uri.getPathSegments().get(1);
+ final File file = guidToFile(guid);
+
+ // TODO: offer as thumbnail
+ return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(uri, mode));
}
default: {
throw new UnsupportedOperationException("Unsupported Uri " + uri);
@@ -189,16 +259,84 @@ public class ExternalStorageProvider extends ContentProvider {
@Override
public Uri insert(Uri uri, ContentValues values) {
- throw new UnsupportedOperationException();
+ switch (sMatcher.match(uri)) {
+ case URI_DOCS_ID: {
+ final String guid = uri.getPathSegments().get(1);
+ final File parent = guidToFile(guid);
+
+ 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 (DocumentsContract.MIME_TYPE_DIRECTORY.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;
+ }
+ }
+
+ return DocumentsContract.buildDocumentUri(AUTHORITY, fileToGuid(file));
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
+ switch (sMatcher.match(uri)) {
+ case URI_DOCS_ID: {
+ final String guid = uri.getPathSegments().get(1);
+ final File file = guidToFile(guid);
+ 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);
+ }
+ }
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
- throw new UnsupportedOperationException();
+ switch (sMatcher.match(uri)) {
+ case URI_DOCS_ID: {
+ final String guid = uri.getPathSegments().get(1);
+ final File file = guidToFile(guid);
+ return file.delete() ? 1 : 0;
+ }
+ default: {
+ throw new UnsupportedOperationException("Unsupported Uri " + uri);
+ }
+ }
+ }
+
+ private String validateDisplayName(String displayName, String mimeType) {
+ if (DocumentsContract.MIME_TYPE_DIRECTORY.equals(mimeType)) {
+ return displayName;
+ } else {
+ // Try appending meaningful extension if needed
+ if (!mimeType.equals(getTypeForName(displayName))) {
+ final String extension = MimeTypeMap.getSingleton()
+ .getExtensionFromMimeType(mimeType);
+ if (extension != null) {
+ displayName += "." + extension;
+ }
+ }
+
+ return displayName;
+ }
}
}