DocumentsProviderpublic abstract class DocumentsProvider extends android.content.ContentProvider Base class for a document provider. A document provider offers read and write
access to durable files, such as files stored on a local disk, or files in a
cloud storage service. To create a document provider, extend this class,
implement the abstract methods, and add it to your manifest like this:
<manifest>
...
<application>
...
<provider
android:name="com.example.MyCloudProvider"
android:authorities="com.example.mycloudprovider"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"
android:enabled="@bool/isAtLeastKitKat">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
</intent-filter>
</provider>
...
</application>
</manifest>
When defining your provider, you must protect it with
{@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
only the system can obtain. Applications cannot use a documents provider
directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
{@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
navigate and select documents. When a user selects documents through that UI,
the system issues narrow URI permission grants to the requesting application.
Documents
A document can be either an openable stream (with a specific MIME type), or a
directory containing additional documents (with the
{@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
of a subtree containing zero or more documents, which can recursively contain
even more documents and directories.
Each document can have different capabilities, as described by
{@link Document#COLUMN_FLAGS}. For example, if a document can be represented
as a thumbnail, your provider can set
{@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
{@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
that thumbnail.
Each document under a provider is uniquely referenced by its
{@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
single document can be included in multiple directories when responding to
{@link #queryChildDocuments(String, String[], String)}. For example, a
provider might surface a single photo in multiple locations: once in a
directory of geographic locations, and again in a directory of dates.
Roots
All documents are surfaced through one or more "roots." Each root represents
the top of a document tree that a user can navigate. For example, a root
could represent an account or a physical storage device. Similar to
documents, each root can have capabilities expressed through
{@link Root#COLUMN_FLAGS}.
|
Fields Summary |
---|
private static final String | TAG | private static final int | MATCH_ROOTS | private static final int | MATCH_ROOT | private static final int | MATCH_RECENT | private static final int | MATCH_SEARCH | private static final int | MATCH_DOCUMENT | private static final int | MATCH_CHILDREN | private static final int | MATCH_DOCUMENT_TREE | private static final int | MATCH_CHILDREN_TREE | private String | mAuthority | private android.content.UriMatcher | mMatcher |
Methods Summary |
---|
public void | attachInfo(android.content.Context context, android.content.pm.ProviderInfo info)Implementation is provided by the parent class.
mAuthority = info.authority;
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
// Sanity check our setup
if (!info.exported) {
throw new SecurityException("Provider must be exported");
}
if (!info.grantUriPermissions) {
throw new SecurityException("Provider must grantUriPermissions");
}
if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
|| !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
}
super.attachInfo(context, info);
| public android.os.Bundle | call(java.lang.String method, java.lang.String arg, android.os.Bundle extras)Implementation is provided by the parent class. Can be overridden to
provide additional functionality, but subclasses must always
call the superclass. If the superclass returns {@code null}, the subclass
may implement custom behavior.
if (!method.startsWith("android:")) {
// Ignore non-platform methods
return super.call(method, arg, extras);
}
final Context context = getContext();
final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
final String authority = documentUri.getAuthority();
final String documentId = DocumentsContract.getDocumentId(documentUri);
if (!mAuthority.equals(authority)) {
throw new SecurityException(
"Requested authority " + authority + " doesn't match provider " + mAuthority);
}
enforceTree(documentUri);
final Bundle out = new Bundle();
try {
if (METHOD_CREATE_DOCUMENT.equals(method)) {
enforceWritePermissionInner(documentUri, null);
final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
final String newDocumentId = createDocument(documentId, mimeType, displayName);
// No need to issue new grants here, since caller either has
// manage permission or a prefix grant. We might generate a
// tree style URI if that's how they called us.
final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
newDocumentId);
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
} else if (METHOD_RENAME_DOCUMENT.equals(method)) {
enforceWritePermissionInner(documentUri, null);
final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
final String newDocumentId = renameDocument(documentId, displayName);
if (newDocumentId != null) {
final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
newDocumentId);
// If caller came in with a narrow grant, issue them a
// narrow grant for the newly renamed document.
if (!isTreeUri(newDocumentUri)) {
final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
documentUri);
context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
}
out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
// Original document no longer exists, clean up any grants
revokeDocumentPermission(documentId);
}
} else if (METHOD_DELETE_DOCUMENT.equals(method)) {
enforceWritePermissionInner(documentUri, null);
deleteDocument(documentId);
// Document no longer exists, clean up any grants
revokeDocumentPermission(documentId);
} else {
throw new UnsupportedOperationException("Method not supported " + method);
}
} catch (FileNotFoundException e) {
throw new IllegalStateException("Failed call " + method, e);
}
return out;
| public android.net.Uri | canonicalize(android.net.Uri uri)Implementation is provided by the parent class. Can be overridden to
provide additional functionality, but subclasses must always
call the superclass. If the superclass returns {@code null}, the subclass
may implement custom behavior.
This is typically used to resolve a subtree URI into a concrete document
reference, issuing a narrower single-document URI permission grant along
the way.
final Context context = getContext();
switch (mMatcher.match(uri)) {
case MATCH_DOCUMENT_TREE:
enforceTree(uri);
final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
// Caller may only have prefix grant, so extend them a grant to
// the narrow URI.
final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
return narrowUri;
}
return null;
| public java.lang.String | createDocument(java.lang.String parentDocumentId, java.lang.String mimeType, java.lang.String displayName)Create a new document and return its newly generated
{@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
{@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
not change once returned.
throw new UnsupportedOperationException("Create not supported");
| public final int | delete(android.net.Uri uri, java.lang.String selection, java.lang.String[] selectionArgs)Implementation is provided by the parent class. Throws by default, and
cannot be overriden.
throw new UnsupportedOperationException("Delete not supported");
| public void | deleteDocument(java.lang.String documentId)Delete the requested 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 using
{@link #revokeDocumentPermission(String)}.
throw new UnsupportedOperationException("Delete not supported");
| private void | enforceTree(android.net.Uri documentUri){@hide}
if (isTreeUri(documentUri)) {
final String parent = getTreeDocumentId(documentUri);
final String child = getDocumentId(documentUri);
if (Objects.equals(parent, child)) {
return;
}
if (!isChildDocument(parent, child)) {
throw new SecurityException(
"Document " + child + " is not a descendant of " + parent);
}
}
| private static int | getCallingOrSelfUriPermissionModeFlags(android.content.Context context, android.net.Uri uri)
// TODO: move this to a direct AMS call
int modeFlags = 0;
if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
}
if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
}
if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
| Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
== PackageManager.PERMISSION_GRANTED) {
modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
}
return modeFlags;
| public java.lang.String | getDocumentType(java.lang.String documentId)Return concrete MIME type of the requested document. Must match the value
of {@link Document#COLUMN_MIME_TYPE} for this document. The default
implementation queries {@link #queryDocument(String, String[])}, so
providers may choose to override this as an optimization.
final Cursor cursor = queryDocument(documentId, null);
try {
if (cursor.moveToFirst()) {
return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
} else {
return null;
}
} finally {
IoUtils.closeQuietly(cursor);
}
| public final java.lang.String | getType(android.net.Uri uri)Implementation is provided by the parent class. Cannot be overriden.
try {
switch (mMatcher.match(uri)) {
case MATCH_ROOT:
return DocumentsContract.Root.MIME_TYPE_ITEM;
case MATCH_DOCUMENT:
case MATCH_DOCUMENT_TREE:
enforceTree(uri);
return getDocumentType(getDocumentId(uri));
default:
return null;
}
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed during getType", e);
return null;
}
| public final android.net.Uri | insert(android.net.Uri uri, android.content.ContentValues values)Implementation is provided by the parent class. Throws by default, and
cannot be overriden.
throw new UnsupportedOperationException("Insert not supported");
| public boolean | isChildDocument(java.lang.String parentDocumentId, java.lang.String documentId)Test if a document is descendant (child, grandchild, etc) from the given
parent. For example, providers must implement this to support
{@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
requests to keep this request fast.
return false;
| public final android.content.res.AssetFileDescriptor | openAssetFile(android.net.Uri uri, java.lang.String mode)Implementation is provided by the parent class. Cannot be overriden.
enforceTree(uri);
final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
| public final android.content.res.AssetFileDescriptor | openAssetFile(android.net.Uri uri, java.lang.String mode, android.os.CancellationSignal signal)Implementation is provided by the parent class. Cannot be overriden.
enforceTree(uri);
final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
| public abstract android.os.ParcelFileDescriptor | openDocument(java.lang.String documentId, java.lang.String mode, android.os.CancellationSignal signal)Open and return the requested document.
Your provider should return a reliable {@link ParcelFileDescriptor} to
detect when the remote caller has finished reading or writing the
document. You may return a pipe or socket pair if the mode is exclusively
"r" or "w", but complex modes like "rw" imply a normal file on disk that
supports seeking.
If you block while downloading content, you should periodically check
{@link CancellationSignal#isCanceled()} to abort abandoned open requests.
| public android.content.res.AssetFileDescriptor | openDocumentThumbnail(java.lang.String documentId, android.graphics.Point sizeHint, android.os.CancellationSignal signal)Open and return a thumbnail of the requested document.
A provider should return a thumbnail closely matching the hinted size,
attempting to serve from a local cache if possible. A provider should
never return images more than double the hinted size.
If you perform expensive operations to download or generate a thumbnail,
you should periodically check {@link CancellationSignal#isCanceled()} to
abort abandoned thumbnail requests.
throw new UnsupportedOperationException("Thumbnails not supported");
| public final android.os.ParcelFileDescriptor | openFile(android.net.Uri uri, java.lang.String mode)Implementation is provided by the parent class. Cannot be overriden.
enforceTree(uri);
return openDocument(getDocumentId(uri), mode, null);
| public final android.os.ParcelFileDescriptor | openFile(android.net.Uri uri, java.lang.String mode, android.os.CancellationSignal signal)Implementation is provided by the parent class. Cannot be overriden.
enforceTree(uri);
return openDocument(getDocumentId(uri), mode, signal);
| public final android.content.res.AssetFileDescriptor | openTypedAssetFile(android.net.Uri uri, java.lang.String mimeTypeFilter, android.os.Bundle opts)Implementation is provided by the parent class. Cannot be overriden.
enforceTree(uri);
if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
} else {
return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
}
| public final android.content.res.AssetFileDescriptor | openTypedAssetFile(android.net.Uri uri, java.lang.String mimeTypeFilter, android.os.Bundle opts, android.os.CancellationSignal signal)Implementation is provided by the parent class. Cannot be overriden.
enforceTree(uri);
if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
} else {
return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
}
| public final android.database.Cursor | query(android.net.Uri uri, java.lang.String[] projection, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sortOrder)Implementation is provided by the parent class. Cannot be overriden.
try {
switch (mMatcher.match(uri)) {
case MATCH_ROOTS:
return queryRoots(projection);
case MATCH_RECENT:
return queryRecentDocuments(getRootId(uri), projection);
case MATCH_SEARCH:
return querySearchDocuments(
getRootId(uri), getSearchDocumentsQuery(uri), projection);
case MATCH_DOCUMENT:
case MATCH_DOCUMENT_TREE:
enforceTree(uri);
return queryDocument(getDocumentId(uri), projection);
case MATCH_CHILDREN:
case MATCH_CHILDREN_TREE:
enforceTree(uri);
if (DocumentsContract.isManageMode(uri)) {
return queryChildDocumentsForManage(
getDocumentId(uri), projection, sortOrder);
} else {
return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
}
default:
throw new UnsupportedOperationException("Unsupported Uri " + uri);
}
} catch (FileNotFoundException e) {
Log.w(TAG, "Failed during query", e);
return null;
}
| public abstract android.database.Cursor | queryChildDocuments(java.lang.String parentDocumentId, java.lang.String[] projection, java.lang.String sortOrder)Return the children documents contained in the requested directory. This
must only return immediate descendants, as additional queries will be
issued to recursively explore the tree.
If your provider is cloud-based, and you have some data cached or pinned
locally, you may return the local data immediately, setting
{@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
you are still fetching additional data. Then, when the network data is
available, you can send a change notification to trigger a requery and
return the complete contents. To return a Cursor with extras, you need to
extend and override {@link Cursor#getExtras()}.
To support change notifications, you must
{@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
Uri, such as
{@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
you can call {@link ContentResolver#notifyChange(Uri,
android.database.ContentObserver, boolean)} with that Uri to send change
notifications.
| public android.database.Cursor | queryChildDocumentsForManage(java.lang.String parentDocumentId, java.lang.String[] projection, java.lang.String sortOrder){@hide}
throw new UnsupportedOperationException("Manage not supported");
| public abstract android.database.Cursor | queryDocument(java.lang.String documentId, java.lang.String[] projection)Return metadata for the single requested document. You should avoid
making network requests to keep this request fast.
| public android.database.Cursor | queryRecentDocuments(java.lang.String rootId, java.lang.String[] projection)Return recently modified documents under the requested root. This will
only be called for roots that advertise
{@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
limited to only return the 64 most recently modified documents.
Recent documents do not support change notifications.
throw new UnsupportedOperationException("Recent not supported");
| public abstract android.database.Cursor | queryRoots(java.lang.String[] projection)Return all roots currently provided. To display to users, you must define
at least one root. You should avoid making network requests to keep this
request fast.
Each root is defined by the metadata columns described in {@link Root},
including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
representing a tree of documents to display under that root.
If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
android.database.ContentObserver, boolean)} with
{@link DocumentsContract#buildRootsUri(String)} to notify the system.
| public android.database.Cursor | querySearchDocuments(java.lang.String rootId, java.lang.String query, java.lang.String[] projection)Return documents that that match the given query under the requested
root. The returned documents should be sorted by relevance in descending
order. How documents are matched against the query string is an
implementation detail left to each provider, but it's suggested that at
least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
case-insensitive fashion.
Only documents may be returned; directories are not supported in search
results.
If your provider is cloud-based, and you have some data cached or pinned
locally, you may return the local data immediately, setting
{@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
you are still fetching additional data. Then, when the network data is
available, you can send a change notification to trigger a requery and
return the complete contents.
To support change notifications, you must
{@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
android.database.ContentObserver, boolean)} with that Uri to send change
notifications.
throw new UnsupportedOperationException("Search not supported");
| public java.lang.String | renameDocument(java.lang.String documentId, java.lang.String displayName)Rename an existing document.
If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
represent the renamed document, generate and return it. Any outstanding
URI permission grants will be updated to point at the new document. If
the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
rename, return {@code null}.
throw new UnsupportedOperationException("Rename not supported");
| public final void | revokeDocumentPermission(java.lang.String documentId)Revoke any active permission grants for the given
{@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
becomes invalid. Follows the same semantics as
{@link Context#revokeUriPermission(Uri, int)}.
final Context context = getContext();
context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
| public final int | update(android.net.Uri uri, android.content.ContentValues values, java.lang.String selection, java.lang.String[] selectionArgs)Implementation is provided by the parent class. Throws by default, and
cannot be overriden.
throw new UnsupportedOperationException("Update not supported");
|
|