diff options
author | Jeff Sharkey <jsharkey@android.com> | 2013-09-03 00:07:17 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2013-09-03 00:07:17 +0000 |
commit | cd6d7deec8742e257a670db8e027f628afdf337e (patch) | |
tree | 5745aabc61c5ed61d0877082c0e5ebd5eafecbab /core/java | |
parent | 61dda198598e95971ca224e3bcb7393f30e45657 (diff) | |
parent | ae9b51bfa313c51a31af30875a71255d7b6d2e61 (diff) | |
download | frameworks_base-cd6d7deec8742e257a670db8e027f628afdf337e.zip frameworks_base-cd6d7deec8742e257a670db8e027f628afdf337e.tar.gz frameworks_base-cd6d7deec8742e257a670db8e027f628afdf337e.tar.bz2 |
Merge "Refactoring of DocumentsContract." into klp-dev
Diffstat (limited to 'core/java')
-rw-r--r-- | core/java/android/provider/DocumentsContract.java | 751 | ||||
-rw-r--r-- | core/java/android/provider/DocumentsProvider.java | 229 |
2 files changed, 534 insertions, 446 deletions
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); - } } |