Add SDK 29 sources.

Test: N/A
Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/provider/DocumentsProvider.java b/android/provider/DocumentsProvider.java
new file mode 100644
index 0000000..2143a0d
--- /dev/null
+++ b/android/provider/DocumentsProvider.java
@@ -0,0 +1,1437 @@
+/*
+ * 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 android.provider;
+
+import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT;
+import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
+import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH;
+import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA;
+import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
+import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
+import static android.provider.DocumentsContract.buildDocumentUri;
+import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
+import static android.provider.DocumentsContract.buildTreeDocumentUri;
+import static android.provider.DocumentsContract.getDocumentId;
+import static android.provider.DocumentsContract.getRootId;
+import static android.provider.DocumentsContract.getTreeDocumentId;
+import static android.provider.DocumentsContract.isTreeUri;
+
+import android.Manifest;
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AuthenticationRequiredException;
+import android.content.ClipDescription;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.MimeTypeFilter;
+import android.content.UriMatcher;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract.Path;
+import android.provider.DocumentsContract.Root;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import libcore.io.IoUtils;
+
+import java.io.FileNotFoundException;
+import java.util.LinkedList;
+import java.util.Objects;
+
+/**
+ * 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:
+ *
+ * <pre class="prettyprint">&lt;manifest&gt;
+ *    ...
+ *    &lt;application&gt;
+ *        ...
+ *        &lt;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"&gt;
+ *            &lt;intent-filter&gt;
+ *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
+ *            &lt;/intent-filter&gt;
+ *        &lt;/provider&gt;
+ *        ...
+ *    &lt;/application&gt;
+ *&lt;/manifest&gt;</pre>
+ * <p>
+ * 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.
+ * </p>
+ * <h3>Documents</h3>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ * <h3>Roots</h3>
+ * <p>
+ * 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}.
+ * </p>
+ *
+ * @see Intent#ACTION_OPEN_DOCUMENT
+ * @see Intent#ACTION_OPEN_DOCUMENT_TREE
+ * @see Intent#ACTION_CREATE_DOCUMENT
+ */
+public abstract class DocumentsProvider extends ContentProvider {
+    private static final String TAG = "DocumentsProvider";
+
+    private static final int MATCH_ROOTS = 1;
+    private static final int MATCH_ROOT = 2;
+    private static final int MATCH_RECENT = 3;
+    private static final int MATCH_SEARCH = 4;
+    private static final int MATCH_DOCUMENT = 5;
+    private static final int MATCH_CHILDREN = 6;
+    private static final int MATCH_DOCUMENT_TREE = 7;
+    private static final int MATCH_CHILDREN_TREE = 8;
+
+    private String mAuthority;
+
+    private UriMatcher mMatcher;
+
+    /**
+     * Implementation is provided by the parent class.
+     */
+    @Override
+    public void attachInfo(Context context, ProviderInfo info) {
+        registerAuthority(info.authority);
+
+        // 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);
+    }
+
+    /** {@hide} */
+    @Override
+    public void attachInfoForTesting(Context context, ProviderInfo info) {
+        registerAuthority(info.authority);
+
+        super.attachInfoForTesting(context, info);
+    }
+
+    private void registerAuthority(String authority) {
+        mAuthority = 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);
+    }
+
+    /**
+     * 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.
+     *
+     * @param parentDocumentId parent to verify against.
+     * @param documentId child to verify.
+     * @return if given document is a descendant of the given parent.
+     * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
+     */
+    public boolean isChildDocument(String parentDocumentId, String documentId) {
+        return false;
+    }
+
+    /** {@hide} */
+    private void enforceTree(Uri documentUri) {
+        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);
+            }
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @param parentDocumentId the parent directory to create the new document
+     *            under.
+     * @param mimeType the concrete MIME type associated with the new document.
+     *            If the MIME type is not supported, the provider must throw.
+     * @param displayName the display name of the new document. The provider may
+     *            alter this name to meet any internal constraints, such as
+     *            avoiding conflicting names.
+
+     * @throws AuthenticationRequiredException If authentication is required from the user (such as
+     *             login credentials), but it is not guaranteed that the client will handle this
+     *             properly.
+     */
+    @SuppressWarnings("unused")
+    public String createDocument(String parentDocumentId, String mimeType, String displayName)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Create not supported");
+    }
+
+    /**
+     * Rename an existing document.
+     * <p>
+     * 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}.
+     *
+     * @param documentId the document to rename.
+     * @param displayName the updated display name of the document. The provider
+     *            may alter this name to meet any internal constraints, such as
+     *            avoiding conflicting names.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    @SuppressWarnings("unused")
+    public String renameDocument(String documentId, String displayName)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Rename not supported");
+    }
+
+    /**
+     * Delete the requested document.
+     * <p>
+     * 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)}.
+     *
+     * @param documentId the document to delete.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    @SuppressWarnings("unused")
+    public void deleteDocument(String documentId) throws FileNotFoundException {
+        throw new UnsupportedOperationException("Delete not supported");
+    }
+
+    /**
+     * Copy the requested document or a document tree.
+     * <p>
+     * Copies a document including all child documents to another location within
+     * the same document provider. Upon completion returns the document id of
+     * the copied document at the target destination. {@code null} must never
+     * be returned.
+     *
+     * @param sourceDocumentId the document to copy.
+     * @param targetParentDocumentId the target document to be copied into as a child.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    @SuppressWarnings("unused")
+    public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Copy not supported");
+    }
+
+    /**
+     * Move the requested document or a document tree.
+     *
+     * <p>Moves a document including all child documents to another location within
+     * the same document provider. Upon completion returns the document id of
+     * the copied document at the target destination. {@code null} must never
+     * be returned.
+     *
+     * <p>It's the responsibility of the provider to revoke grants if the document
+     * is no longer accessible using <code>sourceDocumentId</code>.
+     *
+     * @param sourceDocumentId the document to move.
+     * @param sourceParentDocumentId the parent of the document to move.
+     * @param targetParentDocumentId the target document to be a new parent of the
+     *     source document.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    @SuppressWarnings("unused")
+    public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
+            String targetParentDocumentId)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Move not supported");
+    }
+
+    /**
+     * Removes the requested document or a document tree.
+     *
+     * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
+     * This method is especially useful if the document can be in multiple parents.
+     *
+     * <p>It's the responsibility of the provider to revoke grants if the document is
+     * removed from the last parent, and effectively the document is deleted.
+     *
+     * @param documentId the document to remove.
+     * @param parentDocumentId the parent of the document to move.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    @SuppressWarnings("unused")
+    public void removeDocument(String documentId, String parentDocumentId)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Remove not supported");
+    }
+
+    /**
+     * Finds the canonical path for the requested document. The path must start
+     * from the parent document if parentDocumentId is not null or the root document
+     * if parentDocumentId is null. If there are more than one path to this document,
+     * return the most typical one. Include both the parent document or root document
+     * and the requested document in the returned path.
+     *
+     * <p>This API assumes that document ID has enough info to infer the root.
+     * Different roots should use different document ID to refer to the same
+     * document.
+     *
+     *
+     * @param parentDocumentId the document from which the path starts if not null,
+     *     or null to indicate a path from the root is requested.
+     * @param childDocumentId the document which path is requested.
+     * @return the path of the requested document. If parentDocumentId is null
+     *     returned root ID must not be null. If parentDocumentId is not null
+     *     returned root ID must be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("findDocumentPath not supported.");
+    }
+
+    /**
+     * Creates an intent sender for a web link, if the document is web linkable.
+     * <p>
+     * {@link AuthenticationRequiredException} can be thrown if user does not have
+     * sufficient permission for the linked document. Before any new permissions
+     * are granted for the linked document, a visible UI must be shown, so the
+     * user can explicitly confirm whether the permission grants are expected.
+     * The user must be able to cancel the operation.
+     * <p>
+     * Options passed as an argument may include a list of recipients, such
+     * as email addresses. The provider should reflect these options if possible,
+     * but it's acceptable to ignore them. In either case, confirmation UI must
+     * be shown before any new permission grants are granted.
+     * <p>
+     * It is all right to generate a web link without granting new permissions,
+     * if opening the link would result in a page for requesting permission
+     * access. If it's impossible then the operation must fail by throwing an exception.
+     *
+     * @param documentId the document to create a web link intent for.
+     * @param options additional information, such as list of recipients. Optional.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     *
+     * @see DocumentsContract.Document#FLAG_WEB_LINKABLE
+     * @see android.app.PendingIntent#getIntentSender
+     */
+    public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("createWebLink is not supported.");
+    }
+
+    /**
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     * <p>
+     *
+     * @param projection list of {@link Root} columns to put into the cursor. If
+     *            {@code null} all supported columns should be included.
+     */
+    public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
+
+    /**
+     * 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.
+     * <p>
+     * Recent documents do not support change notifications.
+     *
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @see DocumentsContract#EXTRA_LOADING
+     */
+    @SuppressWarnings("unused")
+    public Cursor queryRecentDocuments(String rootId, String[] projection)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Recent not supported");
+    }
+
+    /**
+     * 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 of
+     * the most recently modified documents.
+     * <p>
+     * If this method is overriden by the concrete DocumentsProvider and
+     * {@link ContentResolver#QUERY_ARG_LIMIT} is specified with a nonnegative
+     * int under queryArgs, the result will be limited by that number and
+     * {@link ContentResolver#QUERY_ARG_LIMIT} will be specified under
+     * {@link ContentResolver#EXTRA_HONORED_ARGS}. Otherwise, a default 64 limit
+     * will be used and no QUERY_ARG* will be specified under
+     * {@link ContentResolver#EXTRA_HONORED_ARGS}.
+     * <p>
+     * Recent documents do not support change notifications.
+     *
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param queryArgs the extra query arguments.
+     * @param signal used by the caller to signal if the request should be
+     *            cancelled. May be null.
+     * @see DocumentsContract#EXTRA_LOADING
+     */
+    @SuppressWarnings("unused")
+    @Nullable
+    public Cursor queryRecentDocuments(
+            @NonNull String rootId, @Nullable String[] projection, @Nullable Bundle queryArgs,
+            @Nullable CancellationSignal signal) throws FileNotFoundException {
+        Preconditions.checkNotNull(rootId, "rootId can not be null");
+
+        Cursor c = queryRecentDocuments(rootId, projection);
+        Bundle extras = new Bundle();
+        c.setExtras(extras);
+        extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[0]);
+        return c;
+    }
+
+    /**
+     * Return metadata for the single requested document. You should avoid
+     * making network requests to keep this request fast.
+     *
+     * @param documentId the document to return.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    public abstract Cursor queryDocument(String documentId, String[] projection)
+            throws FileNotFoundException;
+
+    /**
+     * 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.
+     * <p>
+     * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
+     * should override {@link #queryChildDocuments(String, String[], Bundle)}.
+     * <p>
+     * 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()}.
+     * <p>
+     * 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.
+     *
+     * @param parentDocumentId the directory to return children for.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param sortOrder how to order the rows, formatted as an SQL
+     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
+     *            Passing {@code null} will use the default sort order, which
+     *            may be unordered. This ordering is a hint that can be used to
+     *            prioritize how data is fetched from the network, but UI may
+     *            always enforce a specific ordering.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
+     */
+    public abstract Cursor queryChildDocuments(
+            String parentDocumentId, String[] projection, String sortOrder)
+            throws FileNotFoundException;
+
+    /**
+     * Override this method to return the children documents contained
+     * in the requested directory. This must return immediate descendants only.
+     *
+     * <p>If your provider is cloud-based, and you have data cached
+     * locally, you may return the local data immediately, setting
+     * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras 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()}.
+     *
+     * <p>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.
+     *
+     * @param parentDocumentId the directory to return children for.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param queryArgs Bundle containing sorting information or other
+     *            argument useful to the provider. If no sorting
+     *            information is available, default sorting
+     *            will be used, which may be unordered. See
+     *            {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
+     *            details.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     *
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
+     */
+    public Cursor queryChildDocuments(
+            String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
+            throws FileNotFoundException {
+
+        return queryChildDocuments(
+                parentDocumentId, projection, getSortClause(queryArgs));
+    }
+
+    /** {@hide} */
+    @SuppressWarnings("unused")
+    public Cursor queryChildDocumentsForManage(
+            String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Manage not supported");
+    }
+
+    /**
+     * Return documents 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     *
+     * @param rootId the root to search under.
+     * @param query string to match documents against.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     *
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
+     */
+    @SuppressWarnings("unused")
+    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Search not supported");
+    }
+
+    /**
+     * Return documents 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.
+     * <p>
+     * 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.
+     * <p>
+     * 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.
+     *
+     * @param rootId the root to search under.
+     * @param projection list of {@link Document} columns to put into the
+     *            cursor. If {@code null} all supported columns should be
+     *            included.
+     * @param queryArgs the query arguments.
+     *            {@link DocumentsContract#QUERY_ARG_EXCLUDE_MEDIA},
+     *            {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
+     *            {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
+     *            {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER},
+     *            {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
+     * @return cursor containing search result. Include
+     *         {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
+     *         extras {@link Bundle} when any QUERY_ARG_* value was honored
+     *         during the preparation of the results.
+     *
+     * @see Root#COLUMN_QUERY_ARGS
+     * @see ContentResolver#EXTRA_HONORED_ARGS
+     * @see DocumentsContract#EXTRA_LOADING
+     * @see DocumentsContract#EXTRA_INFO
+     * @see DocumentsContract#EXTRA_ERROR
+     */
+    @SuppressWarnings("unused")
+    @Nullable
+    public Cursor querySearchDocuments(@NonNull String rootId,
+            @Nullable String[] projection, @NonNull Bundle queryArgs) throws FileNotFoundException {
+        Preconditions.checkNotNull(rootId, "rootId can not be null");
+        Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
+        return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
+                projection);
+    }
+
+    /**
+     * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
+     *
+     * @param rootId the root to be ejected.
+     * @see Root#FLAG_SUPPORTS_EJECT
+     */
+    @SuppressWarnings("unused")
+    public void ejectRoot(String rootId) {
+        throw new UnsupportedOperationException("Eject not supported");
+    }
+
+    /**
+     * Returns metadata associated with the document. The type of metadata returned
+     * is specific to the document type. For example the data returned for an image
+     * file will likely consist primarily or solely of EXIF metadata.
+     *
+     * <p>The returned {@link Bundle} will contain zero or more entries depending
+     * on the type of data supported by the document provider.
+     *
+     * <ol>
+     * <li>A {@link DocumentsContract#METADATA_TYPES} containing a {@code String[]} value.
+     *     The string array identifies the type or types of metadata returned. Each
+     *     value in the can be used to access a {@link Bundle} of data
+     *     containing that type of data.
+     * <li>An entry each for each type of returned metadata. Each set of metadata is
+     *     itself represented as a bundle and accessible via a string key naming
+     *     the type of data.
+     * </ol>
+     *
+     * @param documentId get the metadata of the document
+     * @return a Bundle of Bundles.
+     * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
+     */
+    public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Metadata not supported");
+    }
+
+    /**
+     * 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.
+     * <p>
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     */
+    public String getDocumentType(String documentId) throws FileNotFoundException {
+        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);
+        }
+    }
+
+    /**
+     * Open and return the requested document.
+     * <p>
+     * Your provider should return a reliable {@link ParcelFileDescriptor} to
+     * detect when the remote caller has finished reading or writing the
+     * document.
+     * <p>
+     * Mode "r" should always be supported. Provider should throw
+     * {@link UnsupportedOperationException} if the passing mode is not supported.
+     * 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.
+     * <p>
+     * If you block while downloading content, you should periodically check
+     * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
+     *
+     * @param documentId the document to return.
+     * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
+     * @param signal used by the caller to signal if the request should be
+     *            cancelled. May be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
+     *      OnCloseListener)
+     * @see ParcelFileDescriptor#createReliablePipe()
+     * @see ParcelFileDescriptor#createReliableSocketPair()
+     * @see ParcelFileDescriptor#parseMode(String)
+     */
+    public abstract ParcelFileDescriptor openDocument(
+            String documentId,
+            String mode,
+            @Nullable CancellationSignal signal) throws FileNotFoundException;
+
+    /**
+     * Open and return a thumbnail of the requested document.
+     * <p>
+     * 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.
+     * <p>
+     * If you perform expensive operations to download or generate a thumbnail,
+     * you should periodically check {@link CancellationSignal#isCanceled()} to
+     * abort abandoned thumbnail requests.
+     *
+     * @param documentId the document to return.
+     * @param sizeHint hint of the optimal thumbnail dimensions.
+     * @param signal used by the caller to signal if the request should be
+     *            cancelled. May be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     * @see Document#FLAG_SUPPORTS_THUMBNAIL
+     */
+    @SuppressWarnings("unused")
+    public AssetFileDescriptor openDocumentThumbnail(
+            String documentId, Point sizeHint, CancellationSignal signal)
+            throws FileNotFoundException {
+        throw new UnsupportedOperationException("Thumbnails not supported");
+    }
+
+    /**
+     * Open and return the document in a format matching the specified MIME
+     * type filter.
+     * <p>
+     * A provider may perform a conversion if the documents's MIME type is not
+     * matching the specified MIME type filter.
+     * <p>
+     * Virtual documents must have at least one streamable format.
+     *
+     * @param documentId the document to return.
+     * @param mimeTypeFilter the MIME type filter for the requested format. May
+     *            be *\/*, which matches any MIME type.
+     * @param opts extra options from the client. Specific to the content
+     *            provider.
+     * @param signal used by the caller to signal if the request should be
+     *            cancelled. May be null.
+     * @throws AuthenticationRequiredException If authentication is required from
+     *            the user (such as login credentials), but it is not guaranteed
+     *            that the client will handle this properly.
+     * @see #getDocumentStreamTypes(String, String)
+     */
+    @SuppressWarnings("unused")
+    public AssetFileDescriptor openTypedDocument(
+            String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+            throws FileNotFoundException {
+        throw new FileNotFoundException("The requested MIME type is not supported.");
+    }
+
+    @Override
+    public final Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
+        // transport method. We override that, and don't ever delegate to this method.
+        throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
+    }
+
+    /**
+     * WARNING: Sub-classes should not override this method. This method is non-final
+     * solely for the purposes of backwards compatibility.
+     *
+     * @see #queryChildDocuments(String, String[], Bundle),
+     *      {@link #queryDocument(String, String[])},
+     *      {@link #queryRecentDocuments(String, String[])},
+     *      {@link #queryRoots(String[])}, and
+     *      {@link #querySearchDocuments(String, String[], Bundle)}.
+     */
+    @Override
+    public Cursor query(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+        // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
+        // transport method. We override that, and don't ever delegate to this metohd.
+        throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #queryRoots(String[])
+     * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
+     * @see #queryDocument(String, String[])
+     * @see #queryChildDocuments(String, String[], String)
+     * @see #querySearchDocuments(String, String[], Bundle)
+     */
+    @Override
+    public final Cursor query(
+            Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
+        try {
+            switch (mMatcher.match(uri)) {
+                case MATCH_ROOTS:
+                    return queryRoots(projection);
+                case MATCH_RECENT:
+                    return queryRecentDocuments(
+                            getRootId(uri), projection, queryArgs, cancellationSignal);
+                case MATCH_SEARCH:
+                    return querySearchDocuments(getRootId(uri), projection, queryArgs);
+                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)) {
+                        // TODO: Update "ForManage" variant to support query args.
+                        return queryChildDocumentsForManage(
+                                getDocumentId(uri),
+                                projection,
+                                getSortClause(queryArgs));
+                    } else {
+                        return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
+                    }
+                default:
+                    throw new UnsupportedOperationException("Unsupported Uri " + uri);
+            }
+        } catch (FileNotFoundException e) {
+            Log.w(TAG, "Failed during query", e);
+            return null;
+        }
+    }
+
+    private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
+        queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
+        String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
+
+        if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
+            sortClause = ContentResolver.createSqlSortClause(queryArgs);
+        }
+
+        return sortClause;
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #getDocumentType(String)
+     */
+    @Override
+    public final String getType(Uri uri) {
+        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;
+        }
+    }
+
+    /**
+     * Implementation is provided by the parent class. Can be overridden to
+     * provide additional functionality, but subclasses <em>must</em> always
+     * call the superclass. If the superclass returns {@code null}, the subclass
+     * may implement custom behavior.
+     * <p>
+     * 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.
+     *
+     * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
+     */
+    @CallSuper
+    @Override
+    public Uri canonicalize(Uri uri) {
+        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;
+    }
+
+    private static int getCallingOrSelfUriPermissionModeFlags(Context context, 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;
+    }
+
+    /**
+     * Implementation is provided by the parent class. Throws by default, and
+     * cannot be overridden.
+     *
+     * @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 overridden.
+     *
+     * @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 overridden.
+     */
+    @Override
+    public final int update(
+            Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+        throw new UnsupportedOperationException("Update not supported");
+    }
+
+    /**
+     * Implementation is provided by the parent class. Can be overridden to
+     * provide additional functionality, but subclasses <em>must</em> always
+     * call the superclass. If the superclass returns {@code null}, the subclass
+     * may implement custom behavior.
+     */
+    @CallSuper
+    @Override
+    public Bundle call(String method, String arg, Bundle extras) {
+        if (!method.startsWith("android:")) {
+            // Ignore non-platform methods
+            return super.call(method, arg, extras);
+        }
+
+        try {
+            return callUnchecked(method, arg, extras);
+        } catch (FileNotFoundException e) {
+            throw new ParcelableException(e);
+        }
+    }
+
+    private Bundle callUnchecked(String method, String arg, Bundle extras)
+            throws FileNotFoundException {
+
+        final Context context = getContext();
+        final Bundle out = new Bundle();
+
+        if (METHOD_EJECT_ROOT.equals(method)) {
+            // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
+            // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
+            // MANAGE_DOCUMENTS or associated URI permission here instead
+            final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
+            enforceWritePermissionInner(rootUri, getCallingPackage(), null);
+
+            final String rootId = DocumentsContract.getRootId(rootUri);
+            ejectRoot(rootId);
+
+            return out;
+        }
+
+        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);
+        }
+
+        // If the URI is a tree URI performs some validation.
+        enforceTree(documentUri);
+
+        if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
+            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+
+            final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final String childAuthority = childUri.getAuthority();
+            final String childId = DocumentsContract.getDocumentId(childUri);
+
+            out.putBoolean(
+                    DocumentsContract.EXTRA_RESULT,
+                    mAuthority.equals(childAuthority)
+                            && isChildDocument(documentId, childId));
+
+        } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
+            enforceWritePermissionInner(documentUri, getCallingPackage(), 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_CREATE_WEB_LINK_INTENT.equals(method)) {
+            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+
+            final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
+            final IntentSender intentSender = createWebLinkIntent(documentId, options);
+
+            out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
+
+        } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
+            enforceWritePermissionInner(documentUri, getCallingPackage(), 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, getCallingPackage(), null);
+            deleteDocument(documentId);
+
+            // Document no longer exists, clean up any grants.
+            revokeDocumentPermission(documentId);
+
+        } else if (METHOD_COPY_DOCUMENT.equals(method)) {
+            final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+            enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+            enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+
+            final String newDocumentId = copyDocument(documentId, targetId);
+
+            if (newDocumentId != null) {
+                final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+                        newDocumentId);
+
+                if (!isTreeUri(newDocumentUri)) {
+                    final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+                            documentUri);
+                    context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+                }
+
+                out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+            }
+
+        } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
+            final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
+            final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
+            final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
+            final String targetId = DocumentsContract.getDocumentId(targetUri);
+
+            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
+            enforceWritePermissionInner(targetUri, getCallingPackage(), null);
+
+            final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
+
+            if (newDocumentId != null) {
+                final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
+                        newDocumentId);
+
+                if (!isTreeUri(newDocumentUri)) {
+                    final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
+                            documentUri);
+                    context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
+                }
+
+                out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
+            }
+
+        } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
+            final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
+            final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
+
+            enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
+            enforceWritePermissionInner(documentUri, getCallingPackage(), null);
+            removeDocument(documentId, parentSourceId);
+
+            // It's responsibility of the provider to revoke any grants, as the document may be
+            // still attached to another parents.
+        } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
+            final boolean isTreeUri = isTreeUri(documentUri);
+
+            if (isTreeUri) {
+                enforceReadPermissionInner(documentUri, getCallingPackage(), null);
+            } else {
+                getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
+            }
+
+            final String parentDocumentId = isTreeUri
+                    ? DocumentsContract.getTreeDocumentId(documentUri)
+                    : null;
+
+            Path path = findDocumentPath(parentDocumentId, documentId);
+
+            // Ensure provider doesn't leak information to unprivileged callers.
+            if (isTreeUri) {
+                if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
+                    Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
+                            + parentDocumentId + " found: " + path.getPath().get(0));
+
+                    LinkedList<String> docs = new LinkedList<>(path.getPath());
+                    while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
+                        docs.removeFirst();
+                    }
+                    path = new Path(null, docs);
+                }
+
+                if (path.getRootId() != null) {
+                    Log.wtf(TAG, "Provider returns root id :"
+                            + path.getRootId() + " unexpectedly. Erase root id.");
+                    path = new Path(null, path.getPath());
+                }
+            }
+
+            out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
+        } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
+            return getDocumentMetadata(documentId);
+        } else {
+            throw new UnsupportedOperationException("Method not supported " + method);
+        }
+
+        return out;
+    }
+
+    /**
+     * 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)}.
+     */
+    public final void revokeDocumentPermission(String documentId) {
+        final Context context = getContext();
+        context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
+        context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
+    @Override
+    public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+        enforceTree(uri);
+        return openDocument(getDocumentId(uri), mode, null);
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
+    @Override
+    public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
+            throws FileNotFoundException {
+        enforceTree(uri);
+        return openDocument(getDocumentId(uri), mode, signal);
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
+    @Override
+    @SuppressWarnings("resource")
+    public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
+            throws FileNotFoundException {
+        enforceTree(uri);
+        final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
+        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #openDocument(String, String, CancellationSignal)
+     */
+    @Override
+    @SuppressWarnings("resource")
+    public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
+            throws FileNotFoundException {
+        enforceTree(uri);
+        final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
+        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
+     * @see #getDocumentStreamTypes(String, String)
+     */
+    @Override
+    public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
+            throws FileNotFoundException {
+        return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
+    }
+
+    /**
+     * Implementation is provided by the parent class. Cannot be overridden.
+     *
+     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
+     * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
+     * @see #getDocumentStreamTypes(String, String)
+     */
+    @Override
+    public final AssetFileDescriptor openTypedAssetFile(
+            Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+            throws FileNotFoundException {
+        return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
+    }
+
+    /**
+     * Return a list of streamable MIME types matching the filter, which can be passed to
+     * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
+     *
+     * <p>The default implementation returns a MIME type provided by
+     * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
+     * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
+     *
+     * <p>Virtual documents must have at least one streamable format.
+     *
+     * @see #getStreamTypes(Uri, String)
+     * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
+     */
+    public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
+        Cursor cursor = null;
+        try {
+            cursor = queryDocument(documentId, null);
+            if (cursor.moveToFirst()) {
+                final String mimeType =
+                    cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
+                final long flags =
+                    cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
+                if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
+                        MimeTypeFilter.matches(mimeType, mimeTypeFilter)) {
+                    return new String[] { mimeType };
+                }
+            }
+        } catch (FileNotFoundException e) {
+            return null;
+        } finally {
+            IoUtils.closeQuietly(cursor);
+        }
+
+        // No streamable MIME types.
+        return null;
+    }
+
+    /**
+     * Called by a client to determine the types of data streams that this content provider
+     * support for the given URI.
+     *
+     * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
+     *
+     * @see #getDocumentStreamTypes(String, String)
+     */
+    @Override
+    public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
+        enforceTree(uri);
+        return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
+    }
+
+    /**
+     * @hide
+     */
+    private final AssetFileDescriptor openTypedAssetFileImpl(
+            Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
+            throws FileNotFoundException {
+        enforceTree(uri);
+        final String documentId = getDocumentId(uri);
+        if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
+            final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
+            return openDocumentThumbnail(documentId, sizeHint, signal);
+        }
+        if ("*/*".equals(mimeTypeFilter)) {
+             // If they can take anything, the untyped open call is good enough.
+             return openAssetFile(uri, "r");
+        }
+        final String baseType = getType(uri);
+        if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
+            // Use old untyped open call if this provider has a type for this
+            // URI and it matches the request.
+            return openAssetFile(uri, "r");
+        }
+        // For any other yet unhandled case, let the provider subclass handle it.
+        return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
+    }
+}