diff options
17 files changed, 913 insertions, 732 deletions
diff --git a/api/current.txt b/api/current.txt index 8e2b623..b5c7c88 100644 --- a/api/current.txt +++ b/api/current.txt @@ -20781,67 +20781,69 @@ package android.provider { } public final class DocumentsContract { + method public static android.net.Uri buildChildDocumentsUri(java.lang.String, java.lang.String); method public static android.net.Uri buildDocumentUri(java.lang.String, java.lang.String); - method public static java.lang.String getDocId(android.net.Uri); + method public static android.net.Uri buildRecentDocumentsUri(java.lang.String, java.lang.String); + method public static android.net.Uri buildRootsUri(java.lang.String); + method public static android.net.Uri buildSearchDocumentsUri(java.lang.String, java.lang.String, java.lang.String); + method public static android.net.Uri createDocument(android.content.ContentResolver, android.net.Uri, java.lang.String, java.lang.String); + method public static boolean deleteDocument(android.content.ContentResolver, android.net.Uri); + method public static java.lang.String getDocumentId(android.net.Uri); + method public static android.graphics.Bitmap getDocumentThumbnail(android.content.ContentResolver, android.net.Uri, android.graphics.Point, android.os.CancellationSignal); method public static android.net.Uri[] getOpenDocuments(android.content.Context); + method public static java.lang.String getRootId(android.net.Uri); + method public static java.lang.String getSearchDocumentsQuery(android.net.Uri); field public static final java.lang.String EXTRA_ERROR = "error"; field public static final java.lang.String EXTRA_INFO = "info"; field public static final java.lang.String EXTRA_LOADING = "loading"; } - public static abstract interface DocumentsContract.DocumentColumns implements android.provider.OpenableColumns { - field public static final java.lang.String DOC_ID = "doc_id"; - field public static final java.lang.String FLAGS = "flags"; - field public static final java.lang.String ICON = "icon"; - field public static final java.lang.String LAST_MODIFIED = "last_modified"; - field public static final java.lang.String MIME_TYPE = "mime_type"; - field public static final java.lang.String SUMMARY = "summary"; - } - - public static final class DocumentsContract.DocumentRoot implements android.os.Parcelable { - ctor public DocumentsContract.DocumentRoot(); - method public int describeContents(); - method public void writeToParcel(android.os.Parcel, int); - field public static final android.os.Parcelable.Creator CREATOR; + public static final class DocumentsContract.Document { + field public static final java.lang.String COLUMN_DISPLAY_NAME = "_display_name"; + field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id"; + field public static final java.lang.String COLUMN_FLAGS = "flags"; + field public static final java.lang.String COLUMN_ICON = "icon"; + field public static final java.lang.String COLUMN_LAST_MODIFIED = "last_modified"; + field public static final java.lang.String COLUMN_MIME_TYPE = "mime_type"; + field public static final java.lang.String COLUMN_SIZE = "_size"; + field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final int FLAG_DIR_PREFERS_GRID = 32; // 0x20 + field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8 + field public static final int FLAG_DIR_SUPPORTS_SEARCH = 16; // 0x10 + field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 + field public static final int FLAG_SUPPORTS_THUMBNAIL = 1; // 0x1 + field public static final int FLAG_SUPPORTS_WRITE = 2; // 0x2 + field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.document/directory"; + } + + public static final class DocumentsContract.Root { + field public static final java.lang.String COLUMN_AVAILABLE_BYTES = "available_bytes"; + field public static final java.lang.String COLUMN_DOCUMENT_ID = "document_id"; + field public static final java.lang.String COLUMN_FLAGS = "flags"; + field public static final java.lang.String COLUMN_ICON = "icon"; + field public static final java.lang.String COLUMN_ROOT_ID = "root_id"; + field public static final java.lang.String COLUMN_ROOT_TYPE = "root_type"; + field public static final java.lang.String COLUMN_SUMMARY = "summary"; + field public static final java.lang.String COLUMN_TITLE = "title"; + field public static final int FLAG_ADVANCED = 4; // 0x4 field public static final int FLAG_LOCAL_ONLY = 2; // 0x2 + field public static final int FLAG_PROVIDES_AUDIO = 8; // 0x8 + field public static final int FLAG_PROVIDES_IMAGES = 32; // 0x20 + field public static final int FLAG_PROVIDES_VIDEO = 16; // 0x10 field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 field public static final int ROOT_TYPE_DEVICE = 3; // 0x3 - field public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; // 0x4 field public static final int ROOT_TYPE_SERVICE = 1; // 0x1 field public static final int ROOT_TYPE_SHORTCUT = 2; // 0x2 - field public long availableBytes; - field public java.lang.String docId; - field public int flags; - field public int icon; - field public java.lang.String[] mimeTypes; - field public java.lang.String recentDocId; - field public int rootType; - field public java.lang.String summary; - field public java.lang.String title; - } - - public static final class DocumentsContract.Documents { - field public static final int FLAG_PREFERS_GRID = 64; // 0x40 - field public static final int FLAG_SUPPORTS_CREATE = 1; // 0x1 - field public static final int FLAG_SUPPORTS_DELETE = 4; // 0x4 - field public static final int FLAG_SUPPORTS_RENAME = 2; // 0x2 - field public static final int FLAG_SUPPORTS_SEARCH = 16; // 0x10 - field public static final int FLAG_SUPPORTS_THUMBNAIL = 8; // 0x8 - field public static final int FLAG_SUPPORTS_WRITE = 32; // 0x20 - field public static final java.lang.String MIME_TYPE_DIR = "vnd.android.doc/dir"; } public abstract class DocumentsProvider extends android.content.ContentProvider { ctor public DocumentsProvider(); - method public final android.os.Bundle callFromPackage(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle); method public java.lang.String createDocument(java.lang.String, java.lang.String, java.lang.String) throws java.io.FileNotFoundException; method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]); method public void deleteDocument(java.lang.String) throws java.io.FileNotFoundException; - method public abstract java.util.List<android.provider.DocumentsContract.DocumentRoot> getDocumentRoots(); - method public java.lang.String getType(java.lang.String) throws java.io.FileNotFoundException; + method public java.lang.String getDocumentType(java.lang.String) throws java.io.FileNotFoundException; method public final java.lang.String getType(android.net.Uri); method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues); - method public void notifyDocumentRootsChanged(); method public abstract android.os.ParcelFileDescriptor openDocument(java.lang.String, java.lang.String, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public android.content.res.AssetFileDescriptor openDocumentThumbnail(java.lang.String, android.graphics.Point, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.os.ParcelFileDescriptor openFile(android.net.Uri, java.lang.String) throws java.io.FileNotFoundException; @@ -20849,10 +20851,11 @@ package android.provider { method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle) throws java.io.FileNotFoundException; method public final android.content.res.AssetFileDescriptor openTypedAssetFile(android.net.Uri, java.lang.String, android.os.Bundle, android.os.CancellationSignal) throws java.io.FileNotFoundException; method public final android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String); - method public abstract android.database.Cursor queryDocument(java.lang.String) throws java.io.FileNotFoundException; - method public abstract android.database.Cursor queryDocumentChildren(java.lang.String) throws java.io.FileNotFoundException; - method public android.database.Cursor querySearch(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; - method public void renameDocument(java.lang.String, java.lang.String) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryChildDocuments(java.lang.String, java.lang.String[], java.lang.String) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryDocument(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor queryRecentDocuments(java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; + method public abstract android.database.Cursor queryRoots(java.lang.String[]) throws java.io.FileNotFoundException; + method public android.database.Cursor querySearchDocuments(java.lang.String, java.lang.String, java.lang.String[]) throws java.io.FileNotFoundException; method public final int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]); } diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java index ebb7eb8..b97b7c0 100644 --- a/core/java/android/provider/DocumentsContract.java +++ b/core/java/android/provider/DocumentsContract.java @@ -19,7 +19,6 @@ package android.provider; import static android.net.TrafficStats.KB_IN_BYTES; import static libcore.io.OsConstants.SEEK_SET; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -30,16 +29,13 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.os.Parcel; +import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; -import android.os.Parcelable; import android.util.Log; -import com.android.internal.util.Preconditions; import com.google.android.collect.Lists; import libcore.io.ErrnoException; @@ -62,9 +58,12 @@ import java.util.List; public final class DocumentsContract { private static final String TAG = "Documents"; - // content://com.example/docs/12/ - // content://com.example/docs/12/children/ - // content://com.example/docs/12/search/?query=pony + // content://com.example/root/ + // content://com.example/root/sdcard/ + // content://com.example/root/sdcard/recent/ + // content://com.example/document/12/ + // content://com.example/document/12/children/ + // content://com.example/document/12/search/?query=pony private DocumentsContract() { } @@ -75,279 +74,297 @@ public final class DocumentsContract { /** {@hide} */ public static final String ACTION_MANAGE_DOCUMENTS = "android.provider.action.MANAGE_DOCUMENTS"; - /** {@hide} */ - public static final String - ACTION_DOCUMENT_ROOT_CHANGED = "android.provider.action.DOCUMENT_ROOT_CHANGED"; - /** - * Constants for individual documents. + * Constants related to a document, including {@link Cursor} columns names + * and flags. + * <p> + * A document can be either an openable file (with a specific MIME type), or + * a directory containing additional documents (with the + * {@link #MIME_TYPE_DIR} MIME type). + * <p> + * All columns are <em>read-only</em> to client applications. */ - public final static class Documents { - private Documents() { + public final static class Document { + private Document() { } /** - * MIME type of a document which is a directory that may contain additional - * documents. + * Unique ID of a document. This ID is both provided by and interpreted + * by a {@link DocumentsProvider}, and should be treated as an opaque + * value by client applications. + * <p> + * Each document must have a unique ID within a provider, but that + * single document may be included as a child of multiple directories. + * <p> + * A provider must always return durable IDs, since they will be used to + * issue long-term Uri permission grants when an application interacts + * with {@link Intent#ACTION_OPEN_DOCUMENT} and + * {@link Intent#ACTION_CREATE_DOCUMENT}. + * <p> + * Type: STRING */ - public static final String MIME_TYPE_DIR = "vnd.android.doc/dir"; + public static final String COLUMN_DOCUMENT_ID = "document_id"; /** - * Flag indicating that a document is a directory that supports creation of - * new files within it. + * Concrete MIME type of a document. For example, "image/png" or + * "application/pdf" for openable files. A document can also be a + * directory containing additional documents, which is represented with + * the {@link #MIME_TYPE_DIR} MIME type. + * <p> + * Type: STRING * - * @see DocumentColumns#FLAGS + * @see #MIME_TYPE_DIR */ - public static final int FLAG_SUPPORTS_CREATE = 1; + public static final String COLUMN_MIME_TYPE = "mime_type"; /** - * Flag indicating that a document is renamable. + * Display name of a document, used as the primary title displayed to a + * user. + * <p> + * Type: STRING + */ + public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; + + /** + * Summary of a document, which may be shown to a user. The summary may + * be {@code null}. + * <p> + * Type: STRING + */ + public static final String COLUMN_SUMMARY = "summary"; + + /** + * Timestamp when a document was last modified, in milliseconds since + * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. A + * {@link DocumentsProvider} can update this field using events from + * {@link OnCloseListener} or other reliable + * {@link ParcelFileDescriptor} transports. + * <p> + * Type: INTEGER (long) * - * @see DocumentColumns#FLAGS + * @see System#currentTimeMillis() */ - public static final int FLAG_SUPPORTS_RENAME = 1 << 1; + public static final String COLUMN_LAST_MODIFIED = "last_modified"; /** - * Flag indicating that a document is deletable. + * Specific icon resource ID for a document, or {@code null} to use + * platform default icon based on {@link #COLUMN_MIME_TYPE}. + * <p> + * Type: INTEGER (int) + */ + public static final String COLUMN_ICON = "icon"; + + /** + * Flags that apply to a document. + * <p> + * Type: INTEGER (int) * - * @see DocumentColumns#FLAGS + * @see #FLAG_SUPPORTS_WRITE + * @see #FLAG_SUPPORTS_DELETE + * @see #FLAG_SUPPORTS_THUMBNAIL + * @see #FLAG_DIR_PREFERS_GRID + * @see #FLAG_DIR_SUPPORTS_CREATE + * @see #FLAG_DIR_SUPPORTS_SEARCH */ - public static final int FLAG_SUPPORTS_DELETE = 1 << 2; + public static final String COLUMN_FLAGS = "flags"; /** - * Flag indicating that a document can be represented as a thumbnail. + * Size of a document, in bytes, or {@code null} if unknown. + * <p> + * Type: INTEGER (long) + */ + public static final String COLUMN_SIZE = OpenableColumns.SIZE; + + /** + * MIME type of a document which is a directory that may contain + * additional documents. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_MIME_TYPE */ - public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; + public static final String MIME_TYPE_DIR = "vnd.android.document/directory"; /** - * Flag indicating that a document is a directory that supports search. + * Flag indicating that a document can be represented as a thumbnail. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_FLAGS + * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri, + * Point, CancellationSignal) + * @see DocumentsProvider#openDocumentThumbnail(String, Point, + * android.os.CancellationSignal) */ - public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; + public static final int FLAG_SUPPORTS_THUMBNAIL = 1; /** * Flag indicating that a document supports writing. + * <p> + * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT}, + * the calling application is granted both + * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and + * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual + * writability of a document may change over time, for example due to + * remote access changes. This flag indicates that a document client can + * expect {@link ContentResolver#openOutputStream(Uri)} to succeed. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_FLAGS */ - public static final int FLAG_SUPPORTS_WRITE = 1 << 5; + public static final int FLAG_SUPPORTS_WRITE = 1 << 1; /** - * Flag indicating that a document is a directory that prefers its contents - * be shown in a larger format grid. Usually suitable when a directory - * contains mostly pictures. + * Flag indicating that a document is deletable. * - * @see DocumentColumns#FLAGS + * @see #COLUMN_FLAGS + * @see DocumentsContract#deleteDocument(ContentResolver, Uri) + * @see DocumentsProvider#deleteDocument(String) */ - public static final int FLAG_PREFERS_GRID = 1 << 6; - } - - /** - * Extra boolean flag included in a directory {@link Cursor#getExtras()} - * indicating that a document provider is still loading data. For example, a - * provider has returned some results, but is still waiting on an - * outstanding network request. - * - * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, - * boolean) - */ - public static final String EXTRA_LOADING = "loading"; - - /** - * Extra string included in a directory {@link Cursor#getExtras()} - * providing an informational message that should be shown to a user. For - * example, a provider may wish to indicate that not all documents are - * available. - */ - public static final String EXTRA_INFO = "info"; - - /** - * Extra string included in a directory {@link Cursor#getExtras()} providing - * an error message that should be shown to a user. For example, a provider - * may wish to indicate that a network error occurred. The user may choose - * to retry, resulting in a new query. - */ - public static final String EXTRA_ERROR = "error"; - - /** {@hide} */ - public static final String METHOD_GET_ROOTS = "android:getRoots"; - /** {@hide} */ - public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; - /** {@hide} */ - public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; - /** {@hide} */ - public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; - - /** {@hide} */ - public static final String EXTRA_AUTHORITY = "authority"; - /** {@hide} */ - public static final String EXTRA_PACKAGE_NAME = "packageName"; - /** {@hide} */ - public static final String EXTRA_URI = "uri"; - /** {@hide} */ - public static final String EXTRA_ROOTS = "roots"; - /** {@hide} */ - public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - - private static final String PATH_DOCS = "docs"; - private static final String PATH_CHILDREN = "children"; - private static final String PATH_SEARCH = "search"; - - private static final String PARAM_QUERY = "query"; + public static final int FLAG_SUPPORTS_DELETE = 1 << 2; - /** - * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a - * document provider. - */ - public static Uri buildDocumentUri(String authority, String docId) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) - .authority(authority).appendPath(PATH_DOCS).appendPath(docId).build(); - } + /** + * Flag indicating that a document is a directory that supports creation + * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is + * {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + * @see DocumentsContract#createDocument(ContentResolver, Uri, String, + * String) + * @see DocumentsProvider#createDocument(String, String, String) + */ + public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3; - /** - * Build Uri representing the contents of the given directory in a document - * provider. The given document must be {@link Documents#MIME_TYPE_DIR}. - * - * @hide - */ - public static Uri buildChildrenUri(String authority, String docId) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) - .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_CHILDREN).build(); - } + /** + * Flag indicating that a directory supports search. Only valid when + * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + * @see DocumentsProvider#querySearchDocuments(String, String, + * String[]) + */ + public static final int FLAG_DIR_SUPPORTS_SEARCH = 1 << 4; - /** - * Build Uri representing a search for matching documents under a specific - * directory in a document provider. The given document must have - * {@link Documents#FLAG_SUPPORTS_SEARCH}. - * - * @hide - */ - public static Uri buildSearchUri(String authority, String docId, String query) { - return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) - .appendPath(PATH_DOCS).appendPath(docId).appendPath(PATH_SEARCH) - .appendQueryParameter(PARAM_QUERY, query).build(); + /** + * Flag indicating that a directory prefers its contents be shown in a + * larger format grid. Usually suitable when a directory contains mostly + * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is + * {@link #MIME_TYPE_DIR}. + * + * @see #COLUMN_FLAGS + */ + public static final int FLAG_DIR_PREFERS_GRID = 1 << 5; } /** - * Extract the {@link DocumentColumns#DOC_ID} from the given Uri. + * Constants related to a root of documents, including {@link Cursor} + * columns names and flags. + * <p> + * All columns are <em>read-only</em> to client applications. */ - public static String getDocId(Uri documentUri) { - final List<String> paths = documentUri.getPathSegments(); - if (paths.size() < 2) { - throw new IllegalArgumentException("Not a document: " + documentUri); + public final static class Root { + private Root() { } - if (!PATH_DOCS.equals(paths.get(0))) { - throw new IllegalArgumentException("Not a document: " + documentUri); - } - return paths.get(1); - } - - /** {@hide} */ - public static String getSearchQuery(Uri documentUri) { - return documentUri.getQueryParameter(PARAM_QUERY); - } - /** - * Standard columns for document queries. Document providers <em>must</em> - * support at least these columns when queried. - */ - public interface DocumentColumns extends OpenableColumns { /** - * Unique ID for a document. Values <em>must</em> never change once - * returned, since they may used for long-term Uri permission grants. + * Unique ID of a root. This ID is both provided by and interpreted by a + * {@link DocumentsProvider}, and should be treated as an opaque value + * by client applications. * <p> * Type: STRING */ - public static final String DOC_ID = "doc_id"; + public static final String COLUMN_ROOT_ID = "root_id"; /** - * MIME type of a document. + * Type of a root, used for clustering when presenting multiple roots to + * a user. * <p> - * Type: STRING + * Type: INTEGER (int) * - * @see Documents#MIME_TYPE_DIR + * @see #ROOT_TYPE_SERVICE + * @see #ROOT_TYPE_SHORTCUT + * @see #ROOT_TYPE_DEVICE */ - public static final String MIME_TYPE = "mime_type"; + public static final String COLUMN_ROOT_TYPE = "root_type"; /** - * Timestamp when a document was last modified, in milliseconds since - * January 1, 1970 00:00:00.0 UTC, or {@code null} if unknown. Document - * providers can update this field using events from - * {@link OnCloseListener} or other reliable - * {@link ParcelFileDescriptor} transports. + * Flags that apply to a root. * <p> - * Type: INTEGER (long) + * Type: INTEGER (int) * - * @see System#currentTimeMillis() + * @see #FLAG_LOCAL_ONLY + * @see #FLAG_SUPPORTS_CREATE + * @see #FLAG_ADVANCED + * @see #FLAG_PROVIDES_AUDIO + * @see #FLAG_PROVIDES_IMAGES + * @see #FLAG_PROVIDES_VIDEO */ - public static final String LAST_MODIFIED = "last_modified"; + public static final String COLUMN_FLAGS = "flags"; /** - * Specific icon resource for a document, or {@code null} to resolve - * default using {@link #MIME_TYPE}. + * Icon resource ID for a root. * <p> * Type: INTEGER (int) */ - public static final String ICON = "icon"; + public static final String COLUMN_ICON = "icon"; /** - * Summary for a document, or {@code null} to omit. + * Title for a root, which will be shown to a user. * <p> * Type: STRING */ - public static final String SUMMARY = "summary"; + public static final String COLUMN_TITLE = "title"; /** - * Flags that apply to a specific document. + * Summary for this root, which may be shown to a user. The summary may + * be {@code null}. * <p> - * Type: INTEGER (int) + * Type: STRING */ - public static final String FLAGS = "flags"; - } + public static final String COLUMN_SUMMARY = "summary"; - /** - * Metadata about a specific root of documents. - */ - public final static class DocumentRoot implements Parcelable { /** - * Root that represents a storage service, such as a cloud-based - * service. + * Document which is a directory that represents the top directory of + * this root. + * <p> + * Type: STRING * - * @see #rootType + * @see Document#COLUMN_DOCUMENT_ID */ - public static final int ROOT_TYPE_SERVICE = 1; + public static final String COLUMN_DOCUMENT_ID = "document_id"; + + /** + * Number of bytes available in this root, or {@code null} if unknown or + * unbounded. + * <p> + * Type: INTEGER (long) + */ + public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; /** - * Root that represents a shortcut to content that may be available - * elsewhere through another storage root. + * Type of root that represents a storage service, such as a cloud-based + * service. * - * @see #rootType + * @see #COLUMN_ROOT_TYPE */ - public static final int ROOT_TYPE_SHORTCUT = 2; + public static final int ROOT_TYPE_SERVICE = 1; /** - * Root that represents a physical storage device. + * Type of root that represents a shortcut to content that may be + * available elsewhere through another storage root. * - * @see #rootType + * @see #COLUMN_ROOT_TYPE */ - public static final int ROOT_TYPE_DEVICE = 3; + public static final int ROOT_TYPE_SHORTCUT = 2; /** - * Root that represents a physical storage device that should only be - * displayed to advanced users. + * Type of root that represents a physical storage device. * - * @see #rootType + * @see #COLUMN_ROOT_TYPE */ - public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; + public static final int ROOT_TYPE_DEVICE = 3; /** * Flag indicating that at least one directory under this root supports - * creating content. + * creating content. Roots with this flag will be shown when an + * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}. * - * @see #flags + * @see #COLUMN_FLAGS */ public static final int FLAG_SUPPORTS_CREATE = 1; @@ -355,138 +372,201 @@ public final class DocumentsContract { * Flag indicating that this root offers content that is strictly local * on the device. That is, no network requests are made for the content. * - * @see #flags + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_LOCAL_ONLY */ public static final int FLAG_LOCAL_ONLY = 1 << 1; - /** {@hide} */ - public String authority; - /** - * Root type, use for clustering. + * Flag indicating that this root should only be visible to advanced + * users. * - * @see #ROOT_TYPE_SERVICE - * @see #ROOT_TYPE_DEVICE + * @see #COLUMN_FLAGS */ - public int rootType; + public static final int FLAG_ADVANCED = 1 << 2; /** - * Flags for this root. + * Flag indicating that a root offers audio documents. When a user is + * selecting audio, roots not providing audio may be excluded. * - * @see #FLAG_LOCAL_ONLY + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_MIME_TYPES */ - public int flags; + public static final int FLAG_PROVIDES_AUDIO = 1 << 3; /** - * Icon resource ID for this root. + * Flag indicating that a root offers video documents. When a user is + * selecting video, roots not providing video may be excluded. + * + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_MIME_TYPES */ - public int icon; + public static final int FLAG_PROVIDES_VIDEO = 1 << 4; /** - * Title for this root. + * Flag indicating that a root offers image documents. When a user is + * selecting images, roots not providing images may be excluded. + * + * @see #COLUMN_FLAGS + * @see Intent#EXTRA_MIME_TYPES */ - public String title; + public static final int FLAG_PROVIDES_IMAGES = 1 << 5; + } - /** - * Summary for this root. May be {@code null}. - */ - public String summary; + /** + * Optional boolean flag included in a directory {@link Cursor#getExtras()} + * indicating that a document provider is still loading data. For example, a + * provider has returned some results, but is still waiting on an + * outstanding network request. The provider must send a content changed + * notification when loading is finished. + * + * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, + * boolean) + */ + public static final String EXTRA_LOADING = "loading"; - /** - * Document which is a directory that represents the top of this root. - * Must not be {@code null}. - * - * @see DocumentColumns#DOC_ID - */ - public String docId; + /** + * Optional string included in a directory {@link Cursor#getExtras()} + * providing an informational message that should be shown to a user. For + * example, a provider may wish to indicate that not all documents are + * available. + */ + public static final String EXTRA_INFO = "info"; - /** - * Document which is a directory representing recently modified - * documents under this root. This directory should return at most two - * dozen documents modified within the last 90 days. May be {@code null} - * if this root doesn't support recents. - * - * @see DocumentColumns#DOC_ID - */ - public String recentDocId; + /** + * Optional string included in a directory {@link Cursor#getExtras()} + * providing an error message that should be shown to a user. For example, a + * provider may wish to indicate that a network error occurred. The user may + * choose to retry, resulting in a new query. + */ + public static final String EXTRA_ERROR = "error"; - /** - * Number of free bytes of available in this root, or -1 if unknown or - * unbounded. - */ - public long availableBytes; + /** {@hide} */ + public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; + /** {@hide} */ + public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; - /** - * Set of MIME type filters describing the content offered by this root, - * or {@code null} to indicate that all MIME types are supported. For - * example, a provider only supporting audio and video might set this to - * {@code ["audio/*", "video/*"]}. - */ - public String[] mimeTypes; + /** {@hide} */ + public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; - public DocumentRoot() { - } + private static final String PATH_ROOT = "root"; + private static final String PATH_RECENT = "recent"; + private static final String PATH_DOCUMENT = "document"; + private static final String PATH_CHILDREN = "children"; + private static final String PATH_SEARCH = "search"; - /** {@hide} */ - public DocumentRoot(Parcel in) { - rootType = in.readInt(); - flags = in.readInt(); - icon = in.readInt(); - title = in.readString(); - summary = in.readString(); - docId = in.readString(); - recentDocId = in.readString(); - availableBytes = in.readLong(); - mimeTypes = in.readStringArray(); - } + private static final String PARAM_QUERY = "query"; - /** {@hide} */ - public Drawable loadIcon(Context context) { - if (icon != 0) { - if (authority != null) { - final PackageManager pm = context.getPackageManager(); - final ProviderInfo info = pm.resolveContentProvider(authority, 0); - if (info != null) { - return pm.getDrawable(info.packageName, icon, info.applicationInfo); - } - } else { - return context.getResources().getDrawable(icon); - } - } - return null; - } + /** + * Build Uri representing the roots of a document provider. When queried, a + * provider will return one or more rows with columns defined by + * {@link Root}. + * + * @see DocumentsProvider#queryRoots(String[]) + */ + public static Uri buildRootsUri(String authority) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_ROOT).build(); + } - @Override - public int describeContents() { - return 0; - } + /** + * Build Uri representing the recently modified documents of a specific + * root. When queried, a provider will return zero or more rows with columns + * defined by {@link Document}. + * + * @see DocumentsProvider#queryRecentDocuments(String, String[]) + * @see #getRootId(Uri) + */ + public static Uri buildRecentDocumentsUri(String authority, String rootId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) + .appendPath(PATH_RECENT).build(); + } + + /** + * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a + * document provider. When queried, a provider will return a single row with + * columns defined by {@link Document}. + * + * @see DocumentsProvider#queryDocument(String, String[]) + * @see #getDocumentId(Uri) + */ + public static Uri buildDocumentUri(String authority, String documentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) + .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); + } + + /** + * Build Uri representing the children of the given directory in a document + * provider. When queried, a provider will return zero or more rows with + * columns defined by {@link Document}. + * + * @param parentDocumentId the document to return children for, which must + * be a directory with MIME type of + * {@link Document#MIME_TYPE_DIR}. + * @see DocumentsProvider#queryChildDocuments(String, String[], String) + * @see #getDocumentId(Uri) + */ + public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) + .build(); + } - @Override - public void writeToParcel(Parcel dest, int flags) { - Preconditions.checkNotNull(docId); - - dest.writeInt(rootType); - dest.writeInt(flags); - dest.writeInt(icon); - dest.writeString(title); - dest.writeString(summary); - dest.writeString(docId); - dest.writeString(recentDocId); - dest.writeLong(availableBytes); - dest.writeStringArray(mimeTypes); + /** + * Build Uri representing a search for matching documents under a specific + * directory in a document provider. When queried, a provider will return + * zero or more rows with columns defined by {@link Document}. + * + * @param parentDocumentId the document to return children for, which must + * be both a directory with MIME type of + * {@link Document#MIME_TYPE_DIR} and have + * {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set. + * @see DocumentsProvider#querySearchDocuments(String, String, String[]) + * @see #getDocumentId(Uri) + * @see #getSearchDocumentsQuery(Uri) + */ + public static Uri buildSearchDocumentsUri( + String authority, String parentDocumentId, String query) { + return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) + .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH) + .appendQueryParameter(PARAM_QUERY, query).build(); + } + + /** + * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri. + */ + public static String getRootId(Uri rootUri) { + final List<String> paths = rootUri.getPathSegments(); + if (paths.size() < 2) { + throw new IllegalArgumentException("Not a root: " + rootUri); + } + if (!PATH_ROOT.equals(paths.get(0))) { + throw new IllegalArgumentException("Not a root: " + rootUri); } + return paths.get(1); + } - public static final Creator<DocumentRoot> CREATOR = new Creator<DocumentRoot>() { - @Override - public DocumentRoot createFromParcel(Parcel in) { - return new DocumentRoot(in); - } + /** + * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri. + */ + public static String getDocumentId(Uri documentUri) { + final List<String> paths = documentUri.getPathSegments(); + if (paths.size() < 2) { + throw new IllegalArgumentException("Not a document: " + documentUri); + } + if (!PATH_DOCUMENT.equals(paths.get(0))) { + throw new IllegalArgumentException("Not a document: " + documentUri); + } + return paths.get(1); + } - @Override - public DocumentRoot[] newArray(int size) { - return new DocumentRoot[size]; - } - }; + /** + * Extract the search query from a Uri built by + * {@link #buildSearchDocumentsUri(String, String, String)}. + */ + public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { + return searchDocumentsUri.getQueryParameter(PARAM_QUERY); } /** @@ -497,6 +577,7 @@ public final class DocumentsContract { * {@link Intent#ACTION_CREATE_DOCUMENT}. * * @see Context#grantUriPermission(String, Uri, int) + * @see Context#revokeUriPermission(Uri, int) * @see ContentResolver#getIncomingUriPermissionGrants(int, int) */ public static Uri[] getOpenDocuments(Context context) { @@ -520,20 +601,28 @@ public final class DocumentsContract { } /** - * Return thumbnail representing the document at the given URI. Callers are - * responsible for their own in-memory caching. Given document must have - * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set. + * Return thumbnail representing the document at the given Uri. Callers are + * responsible for their own in-memory caching. * + * @param documentUri document to return thumbnail for, which must have + * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. + * @param size optimal thumbnail size desired. A provider may return a + * thumbnail of a different size, but never more than double the + * requested size. + * @param signal signal used to indicate that caller is no longer interested + * in the thumbnail. * @return decoded thumbnail, or {@code null} if problem was encountered. - * @hide + * @see DocumentsProvider#openDocumentThumbnail(String, Point, + * android.os.CancellationSignal) */ - public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) { + public static Bitmap getDocumentThumbnail( + ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { final Bundle openOpts = new Bundle(); openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); AssetFileDescriptor afd = null; try { - afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts); + afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); final FileDescriptor fd = afd.getFileDescriptor(); final long offset = afd.getStartOffset(); @@ -583,38 +672,26 @@ public final class DocumentsContract { } } - /** {@hide} */ - public static List<DocumentRoot> getDocumentRoots(ContentProviderClient client) { - try { - final Bundle out = client.call(METHOD_GET_ROOTS, null, null); - final List<DocumentRoot> roots = out.getParcelableArrayList(EXTRA_ROOTS); - return roots; - } catch (Exception e) { - Log.w(TAG, "Failed to get roots", e); - return null; - } - } - /** - * Create a new document under the given parent document with MIME type and - * display name. + * Create a new document with given MIME type and display name. * - * @param docId document with {@link Documents#FLAG_SUPPORTS_CREATE} + * @param parentDocumentUri directory with + * {@link Document#FLAG_DIR_SUPPORTS_CREATE} * @param mimeType MIME type of new document * @param displayName name of new document * @return newly created document, or {@code null} if failed - * @hide */ - public static String createDocument( - ContentProviderClient client, String docId, String mimeType, String displayName) { + public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, + String mimeType, String displayName) { final Bundle in = new Bundle(); - in.putString(DocumentColumns.DOC_ID, docId); - in.putString(DocumentColumns.MIME_TYPE, mimeType); - in.putString(DocumentColumns.DISPLAY_NAME, displayName); + in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); + in.putString(Document.COLUMN_MIME_TYPE, mimeType); + in.putString(Document.COLUMN_DISPLAY_NAME, displayName); try { - final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); - return out.getString(DocumentColumns.DOC_ID); + final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in); + return buildDocumentUri( + parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); } catch (Exception e) { Log.w(TAG, "Failed to create document", e); return null; @@ -622,40 +699,16 @@ public final class DocumentsContract { } /** - * Rename the given document. - * - * @param docId document with {@link Documents#FLAG_SUPPORTS_RENAME} - * @return document which may have changed due to rename, or {@code null} if - * rename failed. - * @hide - */ - public static String renameDocument( - ContentProviderClient client, String docId, String displayName) { - final Bundle in = new Bundle(); - in.putString(DocumentColumns.DOC_ID, docId); - in.putString(DocumentColumns.DISPLAY_NAME, displayName); - - try { - final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); - return out.getString(DocumentColumns.DOC_ID); - } catch (Exception e) { - Log.w(TAG, "Failed to rename document", e); - return null; - } - } - - /** * Delete the given document. * - * @param docId document with {@link Documents#FLAG_SUPPORTS_DELETE} - * @hide + * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} */ - public static boolean deleteDocument(ContentProviderClient client, String docId) { + public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { final Bundle in = new Bundle(); - in.putString(DocumentColumns.DOC_ID, docId); + in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); try { - client.call(METHOD_DELETE_DOCUMENT, null, in); + final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in); return true; } catch (Exception e) { Log.w(TAG, "Failed to delete document", e); diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java index eeb8c41..09f4866 100644 --- a/core/java/android/provider/DocumentsProvider.java +++ b/core/java/android/provider/DocumentsProvider.java @@ -16,16 +16,12 @@ package android.provider; -import static android.provider.DocumentsContract.ACTION_DOCUMENT_ROOT_CHANGED; -import static android.provider.DocumentsContract.EXTRA_AUTHORITY; -import static android.provider.DocumentsContract.EXTRA_ROOTS; import static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE; import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; -import static android.provider.DocumentsContract.METHOD_GET_ROOTS; -import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; -import static android.provider.DocumentsContract.getDocId; -import static android.provider.DocumentsContract.getSearchQuery; +import static android.provider.DocumentsContract.getDocumentId; +import static android.provider.DocumentsContract.getRootId; +import static android.provider.DocumentsContract.getSearchDocumentsQuery; import android.content.ContentProvider; import android.content.ContentValues; @@ -41,15 +37,12 @@ import android.os.Bundle; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.OnCloseListener; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.DocumentRoot; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; import android.util.Log; import libcore.io.IoUtils; import java.io.FileNotFoundException; -import java.util.List; /** * Base class for a document provider. A document provider should extend this @@ -58,13 +51,13 @@ import java.util.List; * Each document provider expresses one or more "roots" which each serve as the * top-level of a tree. For example, a root could represent an account, or a * physical storage device. Under each root, documents are referenced by - * {@link DocumentColumns#DOC_ID}, which must not change once returned. + * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. * <p> * Documents can be either an openable file (with a specific MIME type), or a * directory containing additional documents (with the - * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different - * capabilities, as described by {@link DocumentColumns#FLAGS}. The same - * {@link DocumentColumns#DOC_ID} can be included in multiple directories. + * {@link Document#MIME_TYPE_DIR} MIME type). Each document can have different + * capabilities, as described by {@link Document#COLUMN_FLAGS}. The same + * {@link Document#COLUMN_DOCUMENT_ID} can be included in multiple directories. * <p> * Document providers must be protected with the * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can @@ -78,22 +71,29 @@ import java.util.List; public abstract class DocumentsProvider extends ContentProvider { private static final String TAG = "DocumentsProvider"; - private static final int MATCH_DOCUMENT = 1; - private static final int MATCH_CHILDREN = 2; - private static final int MATCH_SEARCH = 3; + private static final int MATCH_ROOT = 1; + private static final int MATCH_RECENT = 2; + private static final int MATCH_DOCUMENT = 3; + private static final int MATCH_CHILDREN = 4; + private static final int MATCH_SEARCH = 5; private String mAuthority; private UriMatcher mMatcher; + /** + * Implementation is provided by the parent class. + */ @Override public void attachInfo(Context context, ProviderInfo info) { mAuthority = info.authority; mMatcher = new UriMatcher(UriMatcher.NO_MATCH); - mMatcher.addURI(mAuthority, "docs/*", MATCH_DOCUMENT); - mMatcher.addURI(mAuthority, "docs/*/children", MATCH_CHILDREN); - mMatcher.addURI(mAuthority, "docs/*/search", MATCH_SEARCH); + mMatcher.addURI(mAuthority, "root", MATCH_ROOT); + mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); + mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); + mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); + mMatcher.addURI(mAuthority, "document/*/search", MATCH_SEARCH); // Sanity check our setup if (!info.exported) { @@ -111,83 +111,80 @@ public abstract class DocumentsProvider extends ContentProvider { } /** - * Return list of all document roots provided by this document provider. - * When this list changes, a provider must call - * {@link #notifyDocumentRootsChanged()}. - */ - public abstract List<DocumentRoot> getDocumentRoots(); - - /** - * Create and return a new document. A provider must allocate a new - * {@link DocumentColumns#DOC_ID} to represent the document, which must not - * change once returned. + * Create a new document and return its {@link Document#COLUMN_DOCUMENT_ID}. + * A provider must allocate a new {@link Document#COLUMN_DOCUMENT_ID} to + * represent the document, which must not change once returned. * - * @param docId the parent directory to create the new document under. + * @param documentId the parent directory to create the new document under. * @param mimeType the MIME type associated with the new document. * @param displayName the display name of the new document. */ @SuppressWarnings("unused") - public String createDocument(String docId, String mimeType, String displayName) + public String createDocument(String documentId, String mimeType, String displayName) throws FileNotFoundException { throw new UnsupportedOperationException("Create not supported"); } /** - * Rename the given document. + * Delete the given document. Upon returning, any Uri permission grants for + * the given document will be revoked. If additional documents were deleted + * as a side effect of this call, such as documents inside a directory, the + * implementor is responsible for revoking those permissions. * - * @param docId the document to rename. - * @param displayName the new display name. + * @param documentId the document to delete. */ @SuppressWarnings("unused") - public void renameDocument(String docId, String displayName) throws FileNotFoundException { - throw new UnsupportedOperationException("Rename not supported"); + public void deleteDocument(String documentId) throws FileNotFoundException { + throw new UnsupportedOperationException("Delete not supported"); } - /** - * Delete the given document. - * - * @param docId the document to delete. - */ + public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; + @SuppressWarnings("unused") - public void deleteDocument(String docId) throws FileNotFoundException { - throw new UnsupportedOperationException("Delete not supported"); + public Cursor queryRecentDocuments(String rootId, String[] projection) + throws FileNotFoundException { + throw new UnsupportedOperationException("Recent not supported"); } /** * Return metadata for the given document. A provider should avoid making * network requests to keep this request fast. * - * @param docId the document to return. + * @param documentId the document to return. */ - public abstract Cursor queryDocument(String docId) throws FileNotFoundException; + public abstract Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException; /** * Return the children of the given document which is a directory. * - * @param docId the directory to return children for. + * @param parentDocumentId the directory to return children for. */ - public abstract Cursor queryDocumentChildren(String docId) throws FileNotFoundException; + public abstract Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException; /** * Return documents that that match the given query, starting the search at * the given directory. * - * @param docId the directory to start search at. + * @param parentDocumentId the directory to start search at. */ @SuppressWarnings("unused") - public Cursor querySearch(String docId, String query) throws FileNotFoundException { + public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection) + throws FileNotFoundException { throw new UnsupportedOperationException("Search not supported"); } /** * Return MIME type for the given document. Must match the value of - * {@link DocumentColumns#MIME_TYPE} for this document. + * {@link Document#COLUMN_MIME_TYPE} for this document. */ - public String getType(String docId) throws FileNotFoundException { - final Cursor cursor = queryDocument(docId); + public String getDocumentType(String documentId) throws FileNotFoundException { + final Cursor cursor = queryDocument(documentId, null); try { if (cursor.moveToFirst()) { - return cursor.getString(cursor.getColumnIndexOrThrow(DocumentColumns.MIME_TYPE)); + return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); } else { return null; } @@ -233,7 +230,7 @@ public abstract class DocumentsProvider extends ContentProvider { * @param sizeHint hint of the optimal thumbnail dimensions. * @param signal used by the caller to signal if the request should be * cancelled. - * @see Documents#FLAG_SUPPORTS_THUMBNAIL + * @see Document#FLAG_SUPPORTS_THUMBNAIL */ @SuppressWarnings("unused") public AssetFileDescriptor openDocumentThumbnail( @@ -241,17 +238,31 @@ public abstract class DocumentsProvider extends ContentProvider { throw new UnsupportedOperationException("Thumbnails not supported"); } + /** + * Implementation is provided by the parent class. Cannot be overriden. + * + * @see #queryRoots(String[]) + * @see #queryRecentDocuments(String, String[]) + * @see #queryDocument(String, String[]) + * @see #queryChildDocuments(String, String[], String) + * @see #querySearchDocuments(String, String, String[]) + */ @Override - public final Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, - String sortOrder) { + public final Cursor query(Uri uri, String[] projection, String selection, + String[] selectionArgs, String sortOrder) { try { switch (mMatcher.match(uri)) { + case MATCH_ROOT: + return queryRoots(projection); + case MATCH_RECENT: + return queryRecentDocuments(getRootId(uri), projection); case MATCH_DOCUMENT: - return queryDocument(getDocId(uri)); + return queryDocument(getDocumentId(uri), projection); case MATCH_CHILDREN: - return queryDocumentChildren(getDocId(uri)); + return queryChildDocuments(getDocumentId(uri), projection, sortOrder); case MATCH_SEARCH: - return querySearch(getDocId(uri), getSearchQuery(uri)); + return querySearchDocuments( + getDocumentId(uri), getSearchDocumentsQuery(uri), projection); default: throw new UnsupportedOperationException("Unsupported Uri " + uri); } @@ -261,12 +272,17 @@ public abstract class DocumentsProvider extends ContentProvider { } } + /** + * Implementation is provided by the parent class. Cannot be overriden. + * + * @see #getDocumentType(String) + */ @Override public final String getType(Uri uri) { try { switch (mMatcher.match(uri)) { case MATCH_DOCUMENT: - return getType(getDocId(uri)); + return getDocumentType(getDocumentId(uri)); default: return null; } @@ -276,22 +292,39 @@ public abstract class DocumentsProvider extends ContentProvider { } } + /** + * Implementation is provided by the parent class. Throws by default, and + * cannot be overriden. + * + * @see #createDocument(String, String, String) + */ @Override public final Uri insert(Uri uri, ContentValues values) { throw new UnsupportedOperationException("Insert not supported"); } + /** + * Implementation is provided by the parent class. Throws by default, and + * cannot be overriden. + * + * @see #deleteDocument(String) + */ @Override public final int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Delete not supported"); } + /** + * Implementation is provided by the parent class. Throws by default, and + * cannot be overriden. + */ @Override public final int update( Uri uri, ContentValues values, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Update not supported"); } + /** {@hide} */ @Override public final Bundle callFromPackage( String callingPackage, String method, String arg, Bundle extras) { @@ -300,33 +333,25 @@ public abstract class DocumentsProvider extends ContentProvider { return super.callFromPackage(callingPackage, method, arg, extras); } - // Platform operations require the caller explicitly hold manage - // permission; Uri permissions don't extend management operations. - getContext().enforceCallingOrSelfPermission( - android.Manifest.permission.MANAGE_DOCUMENTS, "Document management"); + // Require that caller can manage given document + final String documentId = extras.getString(Document.COLUMN_DOCUMENT_ID); + final Uri documentUri = DocumentsContract.buildDocumentUri(mAuthority, documentId); + getContext().enforceCallingOrSelfUriPermission( + documentUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, method); final Bundle out = new Bundle(); try { - if (METHOD_GET_ROOTS.equals(method)) { - final List<DocumentRoot> roots = getDocumentRoots(); - out.putParcelableList(EXTRA_ROOTS, roots); - - } else if (METHOD_CREATE_DOCUMENT.equals(method)) { - final String docId = extras.getString(DocumentColumns.DOC_ID); - final String mimeType = extras.getString(DocumentColumns.MIME_TYPE); - final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME); + if (METHOD_CREATE_DOCUMENT.equals(method)) { + final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); + final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); - // TODO: issue Uri grant towards caller - final String newDocId = createDocument(docId, mimeType, displayName); - out.putString(DocumentColumns.DOC_ID, newDocId); - - } else if (METHOD_RENAME_DOCUMENT.equals(method)) { - final String docId = extras.getString(DocumentColumns.DOC_ID); - final String displayName = extras.getString(DocumentColumns.DISPLAY_NAME); - renameDocument(docId, displayName); + // TODO: issue Uri grant towards calling package + // TODO: enforce that package belongs to caller + final String newDocumentId = createDocument(documentId, mimeType, displayName); + out.putString(Document.COLUMN_DOCUMENT_ID, newDocumentId); } else if (METHOD_DELETE_DOCUMENT.equals(method)) { - final String docId = extras.getString(DocumentColumns.DOC_ID); + final String docId = extras.getString(Document.COLUMN_DOCUMENT_ID); deleteDocument(docId); } else { @@ -338,47 +363,57 @@ public abstract class DocumentsProvider extends ContentProvider { return out; } + /** + * Implementation is provided by the parent class. + * + * @see #openDocument(String, String, CancellationSignal) + */ @Override public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - return openDocument(getDocId(uri), mode, null); + return openDocument(getDocumentId(uri), mode, null); } + /** + * Implementation is provided by the parent class. + * + * @see #openDocument(String, String, CancellationSignal) + */ @Override public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException { - return openDocument(getDocId(uri), mode, signal); + return openDocument(getDocumentId(uri), mode, signal); } + /** + * Implementation is provided by the parent class. + * + * @see #openDocumentThumbnail(String, Point, CancellationSignal) + */ @Override public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) throws FileNotFoundException { if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); - return openDocumentThumbnail(getDocId(uri), sizeHint, null); + return openDocumentThumbnail(getDocumentId(uri), sizeHint, null); } else { return super.openTypedAssetFile(uri, mimeTypeFilter, opts); } } + /** + * Implementation is provided by the parent class. + * + * @see #openDocumentThumbnail(String, Point, CancellationSignal) + */ @Override public final AssetFileDescriptor openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) throws FileNotFoundException { if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) { final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE); - return openDocumentThumbnail(getDocId(uri), sizeHint, signal); + return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal); } else { return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal); } } - - /** - * Notify system that {@link #getDocumentRoots()} has changed, usually due to an - * account or device change. - */ - public void notifyDocumentRootsChanged() { - final Intent intent = new Intent(ACTION_DOCUMENT_ROOT_CHANGED); - intent.putExtra(EXTRA_AUTHORITY, mAuthority); - getContext().sendBroadcast(intent); - } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index 6bc554f..e0b8d19 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -20,7 +20,6 @@ import android.app.AlertDialog; import android.app.Dialog; import android.app.DialogFragment; import android.app.FragmentManager; -import android.content.ContentProviderClient; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; @@ -28,13 +27,13 @@ import android.content.DialogInterface.OnClickListener; import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.Toast; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; /** * Dialog to create a new directory. @@ -67,24 +66,17 @@ public class CreateDirectoryFragment extends DialogFragment { final String displayName = text1.getText().toString(); final DocumentsActivity activity = (DocumentsActivity) getActivity(); - final Document cwd = activity.getCurrentDirectory(); + final DocumentInfo cwd = activity.getCurrentDirectory(); - final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( - cwd.uri.getAuthority()); try { - final String docId = DocumentsContract.createDocument(client, - DocumentsContract.getDocId(cwd.uri), Documents.MIME_TYPE_DIR, - displayName); + final Uri childUri = DocumentsContract.createDocument( + resolver, cwd.uri, Document.MIME_TYPE_DIR, displayName); // Navigate into newly created child - final Uri childUri = DocumentsContract.buildDocumentUri( - cwd.uri.getAuthority(), docId); - final Document childDoc = Document.fromUri(resolver, childUri); + final DocumentInfo childDoc = DocumentInfo.fromUri(resolver, childUri); activity.onDocumentPicked(childDoc); } catch (Exception e) { Toast.makeText(context, R.string.save_error, Toast.LENGTH_SHORT).show(); - } finally { - ContentProviderClient.closeQuietly(client); } } }); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 783b6ff..79d20a4 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -61,7 +61,7 @@ import android.widget.TextView; import android.widget.Toast; import com.android.documentsui.DocumentsActivity.DisplayState; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; import com.android.internal.util.Predicate; import com.google.android.collect.Lists; @@ -81,7 +81,7 @@ public class DirectoryFragment extends Fragment { private AbsListView mCurrentView; - private Predicate<Document> mFilter; + private Predicate<DocumentInfo> mFilter; public static final int TYPE_NORMAL = 1; public static final int TYPE_SEARCH = 2; @@ -106,8 +106,8 @@ public class DirectoryFragment extends Fragment { } public static void showSearch(FragmentManager fm, Uri uri, String query) { - final Uri searchUri = DocumentsContract.buildSearchUri( - uri.getAuthority(), DocumentsContract.getDocId(uri), query); + final Uri searchUri = DocumentsContract.buildSearchDocumentsUri( + uri.getAuthority(), DocumentsContract.getDocumentId(uri), query); show(fm, TYPE_SEARCH, searchUri); } @@ -163,21 +163,21 @@ public class DirectoryFragment extends Fragment { Uri contentsUri; if (mType == TYPE_NORMAL) { - contentsUri = DocumentsContract.buildChildrenUri( - uri.getAuthority(), DocumentsContract.getDocId(uri)); + contentsUri = DocumentsContract.buildChildDocumentsUri( + uri.getAuthority(), DocumentsContract.getDocumentId(uri)); } else if (mType == TYPE_RECENT_OPEN) { contentsUri = RecentsProvider.buildRecentOpen(); } else { contentsUri = uri; } - final Comparator<Document> sortOrder; + final Comparator<DocumentInfo> sortOrder; if (state.sortOrder == SORT_ORDER_LAST_MODIFIED || mType == TYPE_RECENT_OPEN) { - sortOrder = new Document.LastModifiedComparator(); + sortOrder = new DocumentInfo.LastModifiedComparator(); } else if (state.sortOrder == SORT_ORDER_DISPLAY_NAME) { - sortOrder = new Document.DisplayNameComparator(); + sortOrder = new DocumentInfo.DisplayNameComparator(); } else if (state.sortOrder == SORT_ORDER_SIZE) { - sortOrder = new Document.SizeComparator(); + sortOrder = new DocumentInfo.SizeComparator(); } else { throw new IllegalArgumentException("Unknown sort order " + state.sortOrder); } @@ -258,7 +258,7 @@ public class DirectoryFragment extends Fragment { private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - final Document doc = mAdapter.getItem(position); + final DocumentInfo doc = mAdapter.getItem(position); if (mFilter.apply(doc)) { ((DocumentsActivity) getActivity()).onDocumentPicked(doc); } @@ -291,11 +291,11 @@ public class DirectoryFragment extends Fragment { @Override public boolean onActionItemClicked(ActionMode mode, MenuItem item) { final SparseBooleanArray checked = mCurrentView.getCheckedItemPositions(); - final ArrayList<Document> docs = Lists.newArrayList(); + final ArrayList<DocumentInfo> docs = Lists.newArrayList(); final int size = checked.size(); for (int i = 0; i < size; i++) { if (checked.valueAt(i)) { - final Document doc = mAdapter.getItem(checked.keyAt(i)); + final DocumentInfo doc = mAdapter.getItem(checked.keyAt(i)); docs.add(doc); } } @@ -328,7 +328,7 @@ public class DirectoryFragment extends Fragment { ActionMode mode, int position, long id, boolean checked) { if (checked) { // Directories cannot be checked - final Document doc = mAdapter.getItem(position); + final DocumentInfo doc = mAdapter.getItem(position); if (doc.isDirectory()) { mCurrentView.setItemChecked(position, false); } @@ -339,9 +339,9 @@ public class DirectoryFragment extends Fragment { } }; - private void onShareDocuments(List<Document> docs) { + private void onShareDocuments(List<DocumentInfo> docs) { final ArrayList<Uri> uris = Lists.newArrayList(); - for (Document doc : docs) { + for (DocumentInfo doc : docs) { uris.add(doc.uri); } @@ -363,12 +363,12 @@ public class DirectoryFragment extends Fragment { startActivity(intent); } - private void onDeleteDocuments(List<Document> docs) { + private void onDeleteDocuments(List<DocumentInfo> docs) { final Context context = getActivity(); final ContentResolver resolver = context.getContentResolver(); boolean hadTrouble = false; - for (Document doc : docs) { + for (DocumentInfo doc : docs) { if (!doc.isDeleteSupported()) { Log.w(TAG, "Skipping " + doc); hadTrouble = true; @@ -396,12 +396,12 @@ public class DirectoryFragment extends Fragment { } private class DocumentsAdapter extends BaseAdapter { - private List<Document> mDocuments; + private List<DocumentInfo> mDocuments; public DocumentsAdapter() { } - public void swapDocuments(List<Document> documents) { + public void swapDocuments(List<DocumentInfo> documents) { mDocuments = documents; if (mDocuments != null && mDocuments.isEmpty()) { @@ -433,7 +433,7 @@ public class DirectoryFragment extends Fragment { } } - final Document doc = getItem(position); + final DocumentInfo doc = getItem(position); final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon); final TextView title = (TextView) convertView.findViewById(android.R.id.title); @@ -507,7 +507,7 @@ public class DirectoryFragment extends Fragment { } @Override - public Document getItem(int position) { + public DocumentInfo getItem(int position) { return mDocuments.get(position); } @@ -538,8 +538,8 @@ public class DirectoryFragment extends Fragment { Bitmap result = null; try { - result = DocumentsContract.getThumbnail( - context.getContentResolver(), uri, mThumbSize); + result = DocumentsContract.getDocumentThumbnail( + context.getContentResolver(), uri, mThumbSize, null); if (result != null) { final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache( context, mThumbSize); diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java index 4ce5ef8..cb92d76 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryLoader.java @@ -28,7 +28,7 @@ import android.net.Uri; import android.os.CancellationSignal; import android.util.Log; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; import com.android.internal.util.Predicate; import com.google.android.collect.Lists; @@ -41,7 +41,7 @@ import java.util.List; class DirectoryResult implements AutoCloseable { Cursor cursor; - List<Document> contents = Lists.newArrayList(); + List<DocumentInfo> contents = Lists.newArrayList(); Exception e; @Override @@ -53,11 +53,11 @@ class DirectoryResult implements AutoCloseable { public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> { private final int mType; - private Predicate<Document> mFilter; - private Comparator<Document> mSortOrder; + private Predicate<DocumentInfo> mFilter; + private Comparator<DocumentInfo> mSortOrder; - public DirectoryLoader(Context context, Uri uri, int type, Predicate<Document> filter, - Comparator<Document> sortOrder) { + public DirectoryLoader(Context context, Uri uri, int type, Predicate<DocumentInfo> filter, + Comparator<DocumentInfo> sortOrder) { super(context, uri); mType = type; mFilter = filter; @@ -84,15 +84,15 @@ public class DirectoryLoader extends UriDerivativeLoader<Uri, DirectoryResult> { result.cursor.registerContentObserver(mObserver); while (cursor.moveToNext()) { - Document doc = null; + DocumentInfo doc = null; switch (mType) { case TYPE_NORMAL: case TYPE_SEARCH: - doc = Document.fromDirectoryCursor(uri, cursor); + doc = DocumentInfo.fromDirectoryCursor(uri, cursor); break; case TYPE_RECENT_OPEN: try { - doc = Document.fromRecentOpenCursor(resolver, cursor); + doc = DocumentInfo.fromRecentOpenCursor(resolver, cursor); } catch (FileNotFoundException e) { Log.w(TAG, "Failed to find recent: " + e); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java index 0ce5968..54f62ef 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentChangedReceiver.java @@ -21,11 +21,10 @@ import static com.android.documentsui.DocumentsActivity.TAG; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.provider.DocumentsContract.DocumentRoot; import android.util.Log; /** - * Handles {@link DocumentRoot} changes which invalidate cached data. + * Handles changes which invalidate cached data. */ public class DocumentChangedReceiver extends BroadcastReceiver { @Override diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index 73ca8fa..912d6dc 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -42,7 +42,6 @@ import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.Bundle; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentRoot; import android.support.v4.app.ActionBarDrawerToggle; import android.support.v4.view.GravityCompat; import android.support.v4.widget.DrawerLayout; @@ -60,8 +59,9 @@ import android.widget.SearchView.OnQueryTextListener; import android.widget.TextView; import android.widget.Toast; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.RootInfo; import java.io.FileNotFoundException; import java.util.Arrays; @@ -160,7 +160,7 @@ public class DocumentsActivity extends Activity { mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); final Uri rootUri = intent.getData(); - final DocumentRoot root = mRoots.findRoot(rootUri); + final RootInfo root = mRoots.findRoot(rootUri); if (root != null) { onRootPicked(root, true); } else { @@ -252,7 +252,7 @@ public class DocumentsActivity extends Activity { mDrawerToggle.setDrawerIndicatorEnabled(true); } else { - final DocumentRoot root = getCurrentRoot(); + final RootInfo root = getCurrentRoot(); actionBar.setIcon(root != null ? root.loadIcon(this) : null); if (mRoots.isRecentsRoot(root)) { @@ -317,7 +317,7 @@ public class DocumentsActivity extends Activity { super.onPrepareOptionsMenu(menu); final FragmentManager fm = getFragmentManager(); - final Document cwd = getCurrentDirectory(); + final DocumentInfo cwd = getCurrentDirectory(); final MenuItem createDir = menu.findItem(R.id.menu_create_dir); final MenuItem search = menu.findItem(R.id.menu_search); @@ -473,7 +473,7 @@ public class DocumentsActivity extends Activity { } }; - public DocumentRoot getCurrentRoot() { + public RootInfo getCurrentRoot() { if (mStack.size() > 0) { return mStack.getRoot(mRoots); } else { @@ -481,7 +481,7 @@ public class DocumentsActivity extends Activity { } } - public Document getCurrentDirectory() { + public DocumentInfo getCurrentDirectory() { return mStack.peek(); } @@ -491,7 +491,7 @@ public class DocumentsActivity extends Activity { private void onCurrentDirectoryChanged() { final FragmentManager fm = getFragmentManager(); - final Document cwd = getCurrentDirectory(); + final DocumentInfo cwd = getCurrentDirectory(); if (cwd == null) { // No directory means recents @@ -533,14 +533,14 @@ public class DocumentsActivity extends Activity { onCurrentDirectoryChanged(); } - public void onRootPicked(DocumentRoot root, boolean closeDrawer) { + public void onRootPicked(RootInfo root, boolean closeDrawer) { // Clear entire backstack and start in new root mStack.clear(); if (!mRoots.isRecentsRoot(root)) { try { - final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.docId); - onDocumentPicked(Document.fromUri(getContentResolver(), uri)); + final Uri uri = DocumentsContract.buildDocumentUri(root.authority, root.documentId); + onDocumentPicked(DocumentInfo.fromUri(getContentResolver(), uri)); } catch (FileNotFoundException e) { } } else { @@ -561,7 +561,7 @@ public class DocumentsActivity extends Activity { finish(); } - public void onDocumentPicked(Document doc) { + public void onDocumentPicked(DocumentInfo doc) { final FragmentManager fm = getFragmentManager(); if (doc.isDirectory()) { // TODO: query display mode user preference for this dir @@ -591,7 +591,7 @@ public class DocumentsActivity extends Activity { } } - public void onDocumentsPicked(List<Document> docs) { + public void onDocumentsPicked(List<DocumentInfo> docs) { if (mAction == ACTION_OPEN || mAction == ACTION_GET_CONTENT) { final int size = docs.size(); final Uri[] uris = new Uri[size]; @@ -602,21 +602,19 @@ public class DocumentsActivity extends Activity { } } - public void onSaveRequested(Document replaceTarget) { + public void onSaveRequested(DocumentInfo replaceTarget) { onFinished(replaceTarget.uri); } public void onSaveRequested(String mimeType, String displayName) { - final Document cwd = getCurrentDirectory(); + final DocumentInfo cwd = getCurrentDirectory(); final String authority = cwd.uri.getAuthority(); final ContentProviderClient client = getContentResolver() .acquireUnstableContentProviderClient(authority); try { - final String docId = DocumentsContract.createDocument(client, - DocumentsContract.getDocId(cwd.uri), mimeType, displayName); - - final Uri childUri = DocumentsContract.buildDocumentUri(authority, docId); + final Uri childUri = DocumentsContract.createDocument( + getContentResolver(), cwd.uri, mimeType, displayName); onFinished(childUri); } catch (Exception e) { Toast.makeText(this, R.string.save_error, Toast.LENGTH_SHORT).show(); @@ -701,7 +699,7 @@ public class DocumentsActivity extends Activity { private void dumpStack() { Log.d(TAG, "Current stack:"); - for (Document doc : mStack) { + for (DocumentInfo doc : mStack) { Log.d(TAG, "--> " + doc); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java index a9929de..15ad061 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java +++ b/packages/DocumentsUI/src/com/android/documentsui/MimePredicate.java @@ -16,10 +16,10 @@ package com.android.documentsui; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; import com.android.internal.util.Predicate; -public class MimePredicate implements Predicate<Document> { +public class MimePredicate implements Predicate<DocumentInfo> { private final String[] mFilters; public MimePredicate(String[] filters) { @@ -27,7 +27,7 @@ public class MimePredicate implements Predicate<Document> { } @Override - public boolean apply(Document doc) { + public boolean apply(DocumentInfo doc) { if (doc.isDirectory()) { return true; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java index 3447a51..f5d87a7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsCreateFragment.java @@ -29,7 +29,6 @@ import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.os.CancellationSignal; -import android.provider.DocumentsContract.DocumentRoot; import android.text.TextUtils.TruncateAt; import android.util.Log; import android.view.LayoutInflater; @@ -43,6 +42,7 @@ import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.RootInfo; import com.google.android.collect.Lists; import libcore.io.IoUtils; @@ -181,7 +181,7 @@ public class RecentsCreateFragment extends Fragment { final View summaryList = convertView.findViewById(R.id.summary_list); final DocumentStack stack = getItem(position); - final DocumentRoot root = stack.getRoot(roots); + final RootInfo root = stack.getRoot(roots); icon.setImageDrawable(root.loadIcon(context)); final StringBuilder builder = new StringBuilder(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index aa21457..880a92b 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -25,17 +25,21 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; +import android.database.Cursor; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentRoot; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; import android.util.Log; +import com.android.documentsui.model.RootInfo; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Objects; import com.google.android.collect.Lists; +import libcore.io.IoUtils; + import java.util.List; /** @@ -48,9 +52,9 @@ public class RootsCache { private final Context mContext; - public List<DocumentRoot> mRoots = Lists.newArrayList(); + public List<RootInfo> mRoots = Lists.newArrayList(); - private DocumentRoot mRecentsRoot; + private RootInfo mRecentsRoot; public RootsCache(Context context) { mContext = context; @@ -66,12 +70,10 @@ public class RootsCache { { // Create special root for recents - final DocumentRoot root = new DocumentRoot(); - root.rootType = DocumentRoot.ROOT_TYPE_SHORTCUT; - root.docId = null; + final RootInfo root = new RootInfo(); + root.rootType = Root.ROOT_TYPE_SHORTCUT; root.icon = R.drawable.ic_dir; root.title = mContext.getString(R.string.root_recent); - root.summary = null; root.availableBytes = -1; mRoots.add(root); @@ -89,28 +91,32 @@ public class RootsCache { // TODO: remove deprecated customRoots flag // TODO: populate roots on background thread, and cache results + final Uri rootsUri = DocumentsContract.buildRootsUri(info.authority); final ContentProviderClient client = resolver .acquireUnstableContentProviderClient(info.authority); + Cursor cursor = null; try { - final List<DocumentRoot> roots = DocumentsContract.getDocumentRoots(client); - for (DocumentRoot root : roots) { - root.authority = info.authority; + cursor = client.query(rootsUri, null, null, null, null); + while (cursor.moveToNext()) { + final RootInfo root = RootInfo.fromRootsCursor(info.authority, cursor); + mRoots.add(root); } - mRoots.addAll(roots); } catch (Exception e) { Log.w(TAG, "Failed to load some roots from " + info.authority + ": " + e); } finally { + IoUtils.closeQuietly(cursor); ContentProviderClient.closeQuietly(client); } } } } - public DocumentRoot findRoot(Uri uri) { + @Deprecated + public RootInfo findRoot(Uri uri) { final String authority = uri.getAuthority(); - final String docId = DocumentsContract.getDocId(uri); - for (DocumentRoot root : mRoots) { - if (Objects.equal(root.authority, authority) && Objects.equal(root.docId, docId)) { + final String docId = DocumentsContract.getDocumentId(uri); + for (RootInfo root : mRoots) { + if (Objects.equal(root.authority, authority) && Objects.equal(root.documentId, docId)) { return root; } } @@ -118,23 +124,23 @@ public class RootsCache { } @GuardedBy("ActivityThread") - public DocumentRoot getRecentsRoot() { + public RootInfo getRecentsRoot() { return mRecentsRoot; } @GuardedBy("ActivityThread") - public boolean isRecentsRoot(DocumentRoot root) { + public boolean isRecentsRoot(RootInfo root) { return mRecentsRoot == root; } @GuardedBy("ActivityThread") - public List<DocumentRoot> getRoots() { + public List<RootInfo> getRoots() { return mRoots; } @GuardedBy("ActivityThread") public static Drawable resolveDocumentIcon(Context context, String mimeType) { - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (Document.MIME_TYPE_DIR.equals(mimeType)) { return context.getResources().getDrawable(R.drawable.ic_dir); } else { final PackageManager pm = context.getPackageManager(); diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index 2cfa841..3102b88 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -26,7 +26,7 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; -import android.provider.DocumentsContract.DocumentRoot; +import android.provider.DocumentsContract.Root; import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; @@ -40,7 +40,8 @@ import android.widget.ListView; import android.widget.TextView; import com.android.documentsui.SectionedListAdapter.SectionAdapter; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; +import com.android.documentsui.model.RootInfo; import java.util.Comparator; import java.util.List; @@ -101,8 +102,8 @@ public class RootsFragment extends Fragment { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final DocumentsActivity activity = DocumentsActivity.get(RootsFragment.this); final Object item = mAdapter.getItem(position); - if (item instanceof DocumentRoot) { - activity.onRootPicked((DocumentRoot) item, true); + if (item instanceof RootInfo) { + activity.onRootPicked((RootInfo) item, true); } else if (item instanceof ResolveInfo) { activity.onAppPicked((ResolveInfo) item); } else { @@ -111,7 +112,7 @@ public class RootsFragment extends Fragment { } }; - private static class RootsAdapter extends ArrayAdapter<DocumentRoot> implements SectionAdapter { + private static class RootsAdapter extends ArrayAdapter<RootInfo> implements SectionAdapter { private int mHeaderId; public RootsAdapter(Context context, int headerId) { @@ -131,15 +132,13 @@ public class RootsFragment extends Fragment { final TextView title = (TextView) convertView.findViewById(android.R.id.title); final TextView summary = (TextView) convertView.findViewById(android.R.id.summary); - final DocumentRoot root = getItem(position); + final RootInfo root = getItem(position); icon.setImageDrawable(root.loadIcon(context)); title.setText(root.title); // Device summary is always available space final String summaryText; - if ((root.rootType == DocumentRoot.ROOT_TYPE_DEVICE - || root.rootType == DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED) - && root.availableBytes >= 0) { + if (root.rootType == Root.ROOT_TYPE_DEVICE && root.availableBytes >= 0) { summaryText = context.getString(R.string.root_available_bytes, Formatter.formatFileSize(context, root.availableBytes)); } else { @@ -215,28 +214,27 @@ public class RootsFragment extends Fragment { private final RootsAdapter mDevicesAdvanced; private final AppsAdapter mApps; - public SectionedRootsAdapter(Context context, List<DocumentRoot> roots, Intent includeApps) { + public SectionedRootsAdapter(Context context, List<RootInfo> roots, Intent includeApps) { mServices = new RootsAdapter(context, R.string.root_type_service); mShortcuts = new RootsAdapter(context, R.string.root_type_shortcut); mDevices = new RootsAdapter(context, R.string.root_type_device); mDevicesAdvanced = new RootsAdapter(context, R.string.root_type_device); mApps = new AppsAdapter(context); - for (DocumentRoot root : roots) { + for (RootInfo root : roots) { Log.d(TAG, "Found rootType=" + root.rootType); switch (root.rootType) { - case DocumentRoot.ROOT_TYPE_SERVICE: + case Root.ROOT_TYPE_SERVICE: mServices.add(root); break; - case DocumentRoot.ROOT_TYPE_SHORTCUT: + case Root.ROOT_TYPE_SHORTCUT: mShortcuts.add(root); break; - case DocumentRoot.ROOT_TYPE_DEVICE: - mDevices.add(root); - mDevicesAdvanced.add(root); - break; - case DocumentRoot.ROOT_TYPE_DEVICE_ADVANCED: + case Root.ROOT_TYPE_DEVICE: mDevicesAdvanced.add(root); + if ((root.flags & Root.FLAG_ADVANCED) == 0) { + mDevices.add(root); + } break; } } @@ -281,14 +279,14 @@ public class RootsFragment extends Fragment { } } - public static class RootComparator implements Comparator<DocumentRoot> { + public static class RootComparator implements Comparator<RootInfo> { @Override - public int compare(DocumentRoot lhs, DocumentRoot rhs) { - final int score = Document.compareToIgnoreCaseNullable(lhs.title, rhs.title); + public int compare(RootInfo lhs, RootInfo rhs) { + final int score = DocumentInfo.compareToIgnoreCaseNullable(lhs.title, rhs.title); if (score != 0) { return score; } else { - return Document.compareToIgnoreCaseNullable(lhs.summary, rhs.summary); + return DocumentInfo.compareToIgnoreCaseNullable(lhs.summary, rhs.summary); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index 7e1a297..8b0a974 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -31,7 +31,7 @@ import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; -import com.android.documentsui.model.Document; +import com.android.documentsui.model.DocumentInfo; /** * Display document title editor and save button. @@ -39,7 +39,7 @@ import com.android.documentsui.model.Document; public class SaveFragment extends Fragment { public static final String TAG = "SaveFragment"; - private Document mReplaceTarget; + private DocumentInfo mReplaceTarget; private EditText mDisplayName; private Button mSave; private boolean mIgnoreNextEdit; @@ -128,7 +128,7 @@ public class SaveFragment extends Fragment { * without changing the filename. Can be set to {@code null} if user * navigates outside the target directory. */ - public void setReplaceTarget(Document replaceTarget) { + public void setReplaceTarget(DocumentInfo replaceTarget) { mReplaceTarget = replaceTarget; if (mReplaceTarget != null) { diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java index 692d171..f6e46a8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/Document.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentInfo.java @@ -20,8 +20,7 @@ import android.content.ContentResolver; import android.database.Cursor; import android.net.Uri; import android.provider.DocumentsContract; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; import com.android.documentsui.RecentsProvider; @@ -31,9 +30,9 @@ import java.io.FileNotFoundException; import java.util.Comparator; /** - * Representation of a single document. + * Representation of a {@link Document}. */ -public class Document { +public class DocumentInfo { public final Uri uri; public final String mimeType; public final String displayName; @@ -42,7 +41,7 @@ public class Document { public final String summary; public final long size; - private Document(Uri uri, String mimeType, String displayName, long lastModified, int flags, + private DocumentInfo(Uri uri, String mimeType, String displayName, long lastModified, int flags, String summary, long size) { this.uri = uri; this.mimeType = mimeType; @@ -53,23 +52,23 @@ public class Document { this.size = size; } - public static Document fromDirectoryCursor(Uri parent, Cursor cursor) { + public static DocumentInfo fromDirectoryCursor(Uri parent, Cursor cursor) { final String authority = parent.getAuthority(); - final String docId = getCursorString(cursor, DocumentColumns.DOC_ID); + final String docId = getCursorString(cursor, Document.COLUMN_DOCUMENT_ID); final Uri uri = DocumentsContract.buildDocumentUri(authority, docId); - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); - final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); - final long size = getCursorLong(cursor, DocumentColumns.SIZE); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); + final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS); + final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY); + final long size = getCursorLong(cursor, Document.COLUMN_SIZE); - return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); + return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size); } @Deprecated - public static Document fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) + public static DocumentInfo fromRecentOpenCursor(ContentResolver resolver, Cursor recentCursor) throws FileNotFoundException { final Uri uri = Uri.parse(getCursorString(recentCursor, RecentsProvider.COL_URI)); final long lastModified = getCursorLong(recentCursor, RecentsProvider.COL_TIMESTAMP); @@ -80,14 +79,14 @@ public class Document { if (!cursor.moveToFirst()) { throw new FileNotFoundException("Missing details for " + uri); } - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS) - & Documents.FLAG_SUPPORTS_THUMBNAIL; - final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); - final long size = getCursorLong(cursor, DocumentColumns.SIZE); - - return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS) + & Document.FLAG_SUPPORTS_THUMBNAIL; + final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY); + final long size = getCursorLong(cursor, Document.COLUMN_SIZE); + + return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size); } catch (Throwable t) { throw asFileNotFoundException(t); } finally { @@ -95,21 +94,21 @@ public class Document { } } - public static Document fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException { + public static DocumentInfo fromUri(ContentResolver resolver, Uri uri) throws FileNotFoundException { Cursor cursor = null; try { cursor = resolver.query(uri, null, null, null, null); if (!cursor.moveToFirst()) { throw new FileNotFoundException("Missing details for " + uri); } - final String mimeType = getCursorString(cursor, DocumentColumns.MIME_TYPE); - final String displayName = getCursorString(cursor, DocumentColumns.DISPLAY_NAME); - final long lastModified = getCursorLong(cursor, DocumentColumns.LAST_MODIFIED); - final int flags = getCursorInt(cursor, DocumentColumns.FLAGS); - final String summary = getCursorString(cursor, DocumentColumns.SUMMARY); - final long size = getCursorLong(cursor, DocumentColumns.SIZE); - - return new Document(uri, mimeType, displayName, lastModified, flags, summary, size); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final String displayName = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME); + final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); + final int flags = getCursorInt(cursor, Document.COLUMN_FLAGS); + final String summary = getCursorString(cursor, Document.COLUMN_SUMMARY); + final long size = getCursorLong(cursor, Document.COLUMN_SIZE); + + return new DocumentInfo(uri, mimeType, displayName, lastModified, flags, summary, size); } catch (Throwable t) { throw asFileNotFoundException(t); } finally { @@ -123,30 +122,30 @@ public class Document { } public boolean isCreateSupported() { - return (flags & Documents.FLAG_SUPPORTS_CREATE) != 0; + return (flags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0; } public boolean isSearchSupported() { - return (flags & Documents.FLAG_SUPPORTS_SEARCH) != 0; + return (flags & Document.FLAG_DIR_SUPPORTS_SEARCH) != 0; } public boolean isThumbnailSupported() { - return (flags & Documents.FLAG_SUPPORTS_THUMBNAIL) != 0; + return (flags & Document.FLAG_SUPPORTS_THUMBNAIL) != 0; } public boolean isDirectory() { - return Documents.MIME_TYPE_DIR.equals(mimeType); + return Document.MIME_TYPE_DIR.equals(mimeType); } public boolean isGridPreferred() { - return (flags & Documents.FLAG_PREFERS_GRID) != 0; + return (flags & Document.FLAG_DIR_PREFERS_GRID) != 0; } public boolean isDeleteSupported() { - return (flags & Documents.FLAG_SUPPORTS_DELETE) != 0; + return (flags & Document.FLAG_SUPPORTS_DELETE) != 0; } - private static String getCursorString(Cursor cursor, String columnName) { + public static String getCursorString(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); return (index != -1) ? cursor.getString(index) : null; } @@ -154,7 +153,7 @@ public class Document { /** * Missing or null values are returned as -1. */ - private static long getCursorLong(Cursor cursor, String columnName) { + public static long getCursorLong(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); if (index == -1) return -1; final String value = cursor.getString(index); @@ -166,14 +165,14 @@ public class Document { } } - private static int getCursorInt(Cursor cursor, String columnName) { + public static int getCursorInt(Cursor cursor, String columnName) { final int index = cursor.getColumnIndex(columnName); return (index != -1) ? cursor.getInt(index) : 0; } - public static class DisplayNameComparator implements Comparator<Document> { + public static class DisplayNameComparator implements Comparator<DocumentInfo> { @Override - public int compare(Document lhs, Document rhs) { + public int compare(DocumentInfo lhs, DocumentInfo rhs) { final boolean leftDir = lhs.isDirectory(); final boolean rightDir = rhs.isDirectory(); @@ -185,16 +184,16 @@ public class Document { } } - public static class LastModifiedComparator implements Comparator<Document> { + public static class LastModifiedComparator implements Comparator<DocumentInfo> { @Override - public int compare(Document lhs, Document rhs) { + public int compare(DocumentInfo lhs, DocumentInfo rhs) { return Long.compare(rhs.lastModified, lhs.lastModified); } } - public static class SizeComparator implements Comparator<Document> { + public static class SizeComparator implements Comparator<DocumentInfo> { @Override - public int compare(Document lhs, Document rhs) { + public int compare(DocumentInfo lhs, DocumentInfo rhs) { return Long.compare(rhs.size, lhs.size); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java index 81f75d2..b123a46 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java +++ b/packages/DocumentsUI/src/com/android/documentsui/model/DocumentStack.java @@ -17,11 +17,10 @@ package com.android.documentsui.model; import static com.android.documentsui.DocumentsActivity.TAG; -import static com.android.documentsui.model.Document.asFileNotFoundException; +import static com.android.documentsui.model.DocumentInfo.asFileNotFoundException; import android.content.ContentResolver; import android.net.Uri; -import android.provider.DocumentsContract.DocumentRoot; import android.util.Log; import com.android.documentsui.RootsCache; @@ -33,10 +32,10 @@ import java.io.FileNotFoundException; import java.util.LinkedList; /** - * Representation of a stack of {@link Document}, usually the result of a + * Representation of a stack of {@link DocumentInfo}, usually the result of a * user-driven traversal. */ -public class DocumentStack extends LinkedList<Document> { +public class DocumentStack extends LinkedList<DocumentInfo> { public static String serialize(DocumentStack stack) { final JSONArray json = new JSONArray(); @@ -55,7 +54,7 @@ public class DocumentStack extends LinkedList<Document> { final JSONArray json = new JSONArray(raw); for (int i = 0; i < json.length(); i++) { final Uri uri = Uri.parse(json.getString(i)); - final Document doc = Document.fromUri(resolver, uri); + final DocumentInfo doc = DocumentInfo.fromUri(resolver, uri); stack.add(doc); } } catch (JSONException e) { @@ -66,7 +65,7 @@ public class DocumentStack extends LinkedList<Document> { return stack; } - public DocumentRoot getRoot(RootsCache roots) { + public RootInfo getRoot(RootsCache roots) { return roots.findRoot(getLast().uri); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java new file mode 100644 index 0000000..f7027f3 --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/model/RootInfo.java @@ -0,0 +1,72 @@ +/* + * 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.model; + +import static com.android.documentsui.model.DocumentInfo.getCursorInt; +import static com.android.documentsui.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.provider.DocumentsContract.Root; + +/** + * Representation of a {@link Root}. + */ +public class RootInfo { + public String authority; + public String rootId; + public int rootType; + public int flags; + public int icon; + public String title; + public String summary; + public String documentId; + public long availableBytes; + + public static RootInfo fromRootsCursor(String authority, Cursor cursor) { + final RootInfo root = new RootInfo(); + root.authority = authority; + root.rootId = getCursorString(cursor, Root.COLUMN_ROOT_ID); + root.rootType = getCursorInt(cursor, Root.COLUMN_ROOT_TYPE); + root.flags = getCursorInt(cursor, Root.COLUMN_FLAGS); + root.icon = getCursorInt(cursor, Root.COLUMN_ICON); + root.title = getCursorString(cursor, Root.COLUMN_TITLE); + root.summary = getCursorString(cursor, Root.COLUMN_SUMMARY); + root.documentId = getCursorString(cursor, Root.COLUMN_DOCUMENT_ID); + root.availableBytes = getCursorLong(cursor, Root.COLUMN_AVAILABLE_BYTES); + return root; + } + + public Drawable loadIcon(Context context) { + if (icon != 0) { + if (authority != null) { + final PackageManager pm = context.getPackageManager(); + final ProviderInfo info = pm.resolveContentProvider(authority, 0); + if (info != null) { + return pm.getDrawable(info.packageName, icon, info.applicationInfo); + } + } else { + return context.getResources().getDrawable(icon); + } + } + return null; + } +} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 583ecc9..de8c29a 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -26,9 +26,8 @@ import android.media.ExifInterface; import android.os.CancellationSignal; import android.os.Environment; import android.os.ParcelFileDescriptor; -import android.provider.DocumentsContract.DocumentColumns; -import android.provider.DocumentsContract.DocumentRoot; -import android.provider.DocumentsContract.Documents; +import android.provider.DocumentsContract.Document; +import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; import android.webkit.MimeTypeMap; @@ -41,7 +40,6 @@ 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 DocumentsProvider { @@ -49,36 +47,54 @@ public class ExternalStorageProvider extends DocumentsProvider { // docId format: root:path/to/file - 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 static final String[] DEFAULT_ROOT_PROJECTION = new String[] { + Root.COLUMN_ROOT_ID, Root.COLUMN_ROOT_TYPE, Root.COLUMN_FLAGS, Root.COLUMN_ICON, + Root.COLUMN_TITLE, Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, + Root.COLUMN_AVAILABLE_BYTES, }; - private ArrayList<DocumentRoot> mRoots; - private HashMap<String, DocumentRoot> mTagToRoot; - private HashMap<String, File> mTagToPath; + private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] { + Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, Document.COLUMN_DISPLAY_NAME, + Document.COLUMN_LAST_MODIFIED, Document.COLUMN_FLAGS, Document.COLUMN_SIZE, + }; + + private static class RootInfo { + public String rootId; + public int rootType; + public int flags; + public int icon; + public String title; + public String docId; + } + + private ArrayList<RootInfo> mRoots; + private HashMap<String, RootInfo> mIdToRoot; + private HashMap<String, File> mIdToPath; @Override public boolean onCreate() { mRoots = Lists.newArrayList(); - mTagToRoot = Maps.newHashMap(); - mTagToPath = Maps.newHashMap(); + mIdToRoot = Maps.newHashMap(); + mIdToPath = Maps.newHashMap(); // TODO: support multiple storage devices try { - final String tag = "primary"; + final String rootId = "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); + mIdToPath.put(rootId, path); + + final RootInfo root = new RootInfo(); + root.rootId = "primary"; + root.rootType = Root.ROOT_TYPE_DEVICE; + root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED + | Root.FLAG_PROVIDES_AUDIO | Root.FLAG_PROVIDES_VIDEO + | Root.FLAG_PROVIDES_IMAGES; root.icon = R.drawable.ic_pdf; - root.flags = DocumentRoot.FLAG_LOCAL_ONLY; + root.title = getContext().getString(R.string.root_internal_storage); + root.docId = getDocIdForFile(path); mRoots.add(root); - mTagToRoot.put(tag, root); + mIdToRoot.put(rootId, root); } catch (FileNotFoundException e) { throw new IllegalStateException(e); } @@ -86,12 +102,20 @@ public class ExternalStorageProvider extends DocumentsProvider { return true; } + private static String[] resolveRootProjection(String[] projection) { + return projection != null ? projection : DEFAULT_ROOT_PROJECTION; + } + + private static String[] resolveDocumentProjection(String[] projection) { + return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; + } + private String getDocIdForFile(File file) throws FileNotFoundException { String path = file.getAbsolutePath(); // Find the most-specific root path Map.Entry<String, File> mostSpecific = null; - for (Map.Entry<String, File> root : mTagToPath.entrySet()) { + for (Map.Entry<String, File> root : mIdToPath.entrySet()) { final String rootPath = root.getValue().getPath(); if (path.startsWith(rootPath) && (mostSpecific == null || rootPath.length() > mostSpecific.getValue().getPath().length())) { @@ -121,7 +145,7 @@ public class ExternalStorageProvider extends DocumentsProvider { final String tag = docId.substring(0, splitIndex); final String path = docId.substring(splitIndex + 1); - File target = mTagToPath.get(tag); + File target = mIdToPath.get(tag); if (target == null) { throw new FileNotFoundException("No root for " + tag); } @@ -143,41 +167,48 @@ public class ExternalStorageProvider extends DocumentsProvider { int flags = 0; if (file.isDirectory()) { - flags |= Documents.FLAG_SUPPORTS_SEARCH; + flags |= Document.FLAG_DIR_SUPPORTS_SEARCH; } if (file.isDirectory() && file.canWrite()) { - flags |= Documents.FLAG_SUPPORTS_CREATE; + flags |= Document.FLAG_DIR_SUPPORTS_CREATE; } if (file.canWrite()) { - flags |= Documents.FLAG_SUPPORTS_WRITE; - flags |= Documents.FLAG_SUPPORTS_RENAME; - flags |= Documents.FLAG_SUPPORTS_DELETE; + flags |= Document.FLAG_SUPPORTS_WRITE; + flags |= Document.FLAG_SUPPORTS_DELETE; } final String displayName = file.getName(); final String mimeType = getTypeForFile(file); if (mimeType.startsWith("image/")) { - flags |= Documents.FLAG_SUPPORTS_THUMBNAIL; + flags |= Document.FLAG_SUPPORTS_THUMBNAIL; } final RowBuilder row = result.newRow(); - row.offer(DocumentColumns.DOC_ID, docId); - row.offer(DocumentColumns.DISPLAY_NAME, displayName); - row.offer(DocumentColumns.SIZE, file.length()); - row.offer(DocumentColumns.MIME_TYPE, mimeType); - row.offer(DocumentColumns.LAST_MODIFIED, file.lastModified()); - row.offer(DocumentColumns.FLAGS, flags); + row.offer(Document.COLUMN_DOCUMENT_ID, docId); + row.offer(Document.COLUMN_DISPLAY_NAME, displayName); + row.offer(Document.COLUMN_SIZE, file.length()); + row.offer(Document.COLUMN_MIME_TYPE, mimeType); + row.offer(Document.COLUMN_LAST_MODIFIED, file.lastModified()); + row.offer(Document.COLUMN_FLAGS, flags); } @Override - 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(); + public Cursor queryRoots(String[] projection) throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); + for (String rootId : mIdToPath.keySet()) { + final RootInfo root = mIdToRoot.get(rootId); + final File path = mIdToPath.get(rootId); + + final RowBuilder row = result.newRow(); + row.offer(Root.COLUMN_ROOT_ID, root.rootId); + row.offer(Root.COLUMN_ROOT_TYPE, root.rootType); + row.offer(Root.COLUMN_FLAGS, root.flags); + row.offer(Root.COLUMN_ICON, root.icon); + row.offer(Root.COLUMN_TITLE, root.title); + row.offer(Root.COLUMN_DOCUMENT_ID, root.docId); + row.offer(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace()); } - return mRoots; + return result; } @Override @@ -187,7 +218,7 @@ public class ExternalStorageProvider extends DocumentsProvider { displayName = validateDisplayName(mimeType, displayName); final File file = new File(parent, displayName); - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (Document.MIME_TYPE_DIR.equals(mimeType)) { if (!file.mkdir()) { throw new IllegalStateException("Failed to mkdir " + file); } @@ -204,16 +235,6 @@ public class ExternalStorageProvider extends DocumentsProvider { } @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); - } - // TODO: update any outstanding grants - } - - @Override public void deleteDocument(String docId) throws FileNotFoundException { final File file = getFileForDocId(docId); if (!file.delete()) { @@ -222,16 +243,19 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public Cursor queryDocument(String docId) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); - includeFile(result, docId, null); + public Cursor queryDocument(String documentId, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + includeFile(result, documentId, null); return result; } @Override - public Cursor queryDocumentChildren(String docId) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); - final File parent = getFileForDocId(docId); + public Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(parentDocumentId); for (File file : parent.listFiles()) { includeFile(result, null, file); } @@ -239,9 +263,10 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public Cursor querySearch(String docId, String query) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(SUPPORTED_COLUMNS); - final File parent = getFileForDocId(docId); + public Cursor querySearchDocuments(String parentDocumentId, String query, String[] projection) + throws FileNotFoundException { + final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(parentDocumentId); final LinkedList<File> pending = new LinkedList<File>(); pending.add(parent); @@ -261,22 +286,24 @@ public class ExternalStorageProvider extends DocumentsProvider { } @Override - public String getType(String docId) throws FileNotFoundException { - final File file = getFileForDocId(docId); + public String getDocumentType(String documentId) throws FileNotFoundException { + final File file = getFileForDocId(documentId); return getTypeForFile(file); } @Override - public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) + public ParcelFileDescriptor openDocument( + String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { - final File file = getFileForDocId(docId); + final File file = getFileForDocId(documentId); return ParcelFileDescriptor.open(file, ContentResolver.modeToMode(null, mode)); } @Override public AssetFileDescriptor openDocumentThumbnail( - String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { - final File file = getFileForDocId(docId); + String documentId, Point sizeHint, CancellationSignal signal) + throws FileNotFoundException { + final File file = getFileForDocId(documentId); final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( file, ParcelFileDescriptor.MODE_READ_ONLY); @@ -294,7 +321,7 @@ public class ExternalStorageProvider extends DocumentsProvider { private static String getTypeForFile(File file) { if (file.isDirectory()) { - return Documents.MIME_TYPE_DIR; + return Document.MIME_TYPE_DIR; } else { return getTypeForName(file.getName()); } @@ -314,7 +341,7 @@ public class ExternalStorageProvider extends DocumentsProvider { } private static String validateDisplayName(String mimeType, String displayName) { - if (Documents.MIME_TYPE_DIR.equals(mimeType)) { + if (Document.MIME_TYPE_DIR.equals(mimeType)) { return displayName; } else { // Try appending meaningful extension if needed |