Home | History | Annotate | Download | only in provider
      1 /*
      2  * Copyright (C) 2013 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.provider;
     18 
     19 import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
     20 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
     21 import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT;
     22 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
     23 import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
     24 import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH;
     25 import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA;
     26 import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
     27 import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
     28 import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
     29 import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
     30 import static android.provider.DocumentsContract.buildDocumentUri;
     31 import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
     32 import static android.provider.DocumentsContract.buildTreeDocumentUri;
     33 import static android.provider.DocumentsContract.getDocumentId;
     34 import static android.provider.DocumentsContract.getRootId;
     35 import static android.provider.DocumentsContract.getTreeDocumentId;
     36 import static android.provider.DocumentsContract.isTreeUri;
     37 
     38 import android.Manifest;
     39 import android.annotation.CallSuper;
     40 import android.annotation.NonNull;
     41 import android.annotation.Nullable;
     42 import android.app.AuthenticationRequiredException;
     43 import android.content.ClipDescription;
     44 import android.content.ContentProvider;
     45 import android.content.ContentResolver;
     46 import android.content.ContentValues;
     47 import android.content.Context;
     48 import android.content.Intent;
     49 import android.content.IntentSender;
     50 import android.content.MimeTypeFilter;
     51 import android.content.UriMatcher;
     52 import android.content.pm.PackageManager;
     53 import android.content.pm.ProviderInfo;
     54 import android.content.res.AssetFileDescriptor;
     55 import android.database.Cursor;
     56 import android.graphics.Point;
     57 import android.net.Uri;
     58 import android.os.Bundle;
     59 import android.os.CancellationSignal;
     60 import android.os.ParcelFileDescriptor;
     61 import android.os.ParcelableException;
     62 import android.provider.DocumentsContract.Document;
     63 import android.provider.DocumentsContract.Path;
     64 import android.provider.DocumentsContract.Root;
     65 import android.util.Log;
     66 
     67 import com.android.internal.util.Preconditions;
     68 
     69 import libcore.io.IoUtils;
     70 
     71 import java.io.FileNotFoundException;
     72 import java.util.LinkedList;
     73 import java.util.Objects;
     74 
     75 /**
     76  * Base class for a document provider. A document provider offers read and write
     77  * access to durable files, such as files stored on a local disk, or files in a
     78  * cloud storage service. To create a document provider, extend this class,
     79  * implement the abstract methods, and add it to your manifest like this:
     80  *
     81  * <pre class="prettyprint">&lt;manifest&gt;
     82  *    ...
     83  *    &lt;application&gt;
     84  *        ...
     85  *        &lt;provider
     86  *            android:name="com.example.MyCloudProvider"
     87  *            android:authorities="com.example.mycloudprovider"
     88  *            android:exported="true"
     89  *            android:grantUriPermissions="true"
     90  *            android:permission="android.permission.MANAGE_DOCUMENTS"
     91  *            android:enabled="@bool/isAtLeastKitKat"&gt;
     92  *            &lt;intent-filter&gt;
     93  *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
     94  *            &lt;/intent-filter&gt;
     95  *        &lt;/provider&gt;
     96  *        ...
     97  *    &lt;/application&gt;
     98  *&lt;/manifest&gt;</pre>
     99  * <p>
    100  * When defining your provider, you must protect it with
    101  * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
    102  * only the system can obtain. Applications cannot use a documents provider
    103  * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
    104  * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
    105  * navigate and select documents. When a user selects documents through that UI,
    106  * the system issues narrow URI permission grants to the requesting application.
    107  * </p>
    108  * <h3>Documents</h3>
    109  * <p>
    110  * A document can be either an openable stream (with a specific MIME type), or a
    111  * directory containing additional documents (with the
    112  * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
    113  * of a subtree containing zero or more documents, which can recursively contain
    114  * even more documents and directories.
    115  * </p>
    116  * <p>
    117  * Each document can have different capabilities, as described by
    118  * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
    119  * as a thumbnail, your provider can set
    120  * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
    121  * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
    122  * that thumbnail.
    123  * </p>
    124  * <p>
    125  * Each document under a provider is uniquely referenced by its
    126  * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
    127  * single document can be included in multiple directories when responding to
    128  * {@link #queryChildDocuments(String, String[], String)}. For example, a
    129  * provider might surface a single photo in multiple locations: once in a
    130  * directory of geographic locations, and again in a directory of dates.
    131  * </p>
    132  * <h3>Roots</h3>
    133  * <p>
    134  * All documents are surfaced through one or more "roots." Each root represents
    135  * the top of a document tree that a user can navigate. For example, a root
    136  * could represent an account or a physical storage device. Similar to
    137  * documents, each root can have capabilities expressed through
    138  * {@link Root#COLUMN_FLAGS}.
    139  * </p>
    140  *
    141  * @see Intent#ACTION_OPEN_DOCUMENT
    142  * @see Intent#ACTION_OPEN_DOCUMENT_TREE
    143  * @see Intent#ACTION_CREATE_DOCUMENT
    144  */
    145 public abstract class DocumentsProvider extends ContentProvider {
    146     private static final String TAG = "DocumentsProvider";
    147 
    148     private static final int MATCH_ROOTS = 1;
    149     private static final int MATCH_ROOT = 2;
    150     private static final int MATCH_RECENT = 3;
    151     private static final int MATCH_SEARCH = 4;
    152     private static final int MATCH_DOCUMENT = 5;
    153     private static final int MATCH_CHILDREN = 6;
    154     private static final int MATCH_DOCUMENT_TREE = 7;
    155     private static final int MATCH_CHILDREN_TREE = 8;
    156 
    157     private String mAuthority;
    158 
    159     private UriMatcher mMatcher;
    160 
    161     /**
    162      * Implementation is provided by the parent class.
    163      */
    164     @Override
    165     public void attachInfo(Context context, ProviderInfo info) {
    166         registerAuthority(info.authority);
    167 
    168         // Sanity check our setup
    169         if (!info.exported) {
    170             throw new SecurityException("Provider must be exported");
    171         }
    172         if (!info.grantUriPermissions) {
    173             throw new SecurityException("Provider must grantUriPermissions");
    174         }
    175         if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
    176                 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
    177             throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
    178         }
    179 
    180         super.attachInfo(context, info);
    181     }
    182 
    183     /** {@hide} */
    184     @Override
    185     public void attachInfoForTesting(Context context, ProviderInfo info) {
    186         registerAuthority(info.authority);
    187 
    188         super.attachInfoForTesting(context, info);
    189     }
    190 
    191     private void registerAuthority(String authority) {
    192         mAuthority = authority;
    193 
    194         mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    195         mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
    196         mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
    197         mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
    198         mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
    199         mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
    200         mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
    201         mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
    202         mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
    203     }
    204 
    205     /**
    206      * Test if a document is descendant (child, grandchild, etc) from the given
    207      * parent. For example, providers must implement this to support
    208      * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
    209      * requests to keep this request fast.
    210      *
    211      * @param parentDocumentId parent to verify against.
    212      * @param documentId child to verify.
    213      * @return if given document is a descendant of the given parent.
    214      * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
    215      */
    216     public boolean isChildDocument(String parentDocumentId, String documentId) {
    217         return false;
    218     }
    219 
    220     /** {@hide} */
    221     private void enforceTree(Uri documentUri) {
    222         if (isTreeUri(documentUri)) {
    223             final String parent = getTreeDocumentId(documentUri);
    224             final String child = getDocumentId(documentUri);
    225             if (Objects.equals(parent, child)) {
    226                 return;
    227             }
    228             if (!isChildDocument(parent, child)) {
    229                 throw new SecurityException(
    230                         "Document " + child + " is not a descendant of " + parent);
    231             }
    232         }
    233     }
    234 
    235     /**
    236      * Create a new document and return its newly generated
    237      * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
    238      * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
    239      * not change once returned.
    240      *
    241      * @param parentDocumentId the parent directory to create the new document
    242      *            under.
    243      * @param mimeType the concrete MIME type associated with the new document.
    244      *            If the MIME type is not supported, the provider must throw.
    245      * @param displayName the display name of the new document. The provider may
    246      *            alter this name to meet any internal constraints, such as
    247      *            avoiding conflicting names.
    248 
    249      * @throws AuthenticationRequiredException If authentication is required from the user (such as
    250      *             login credentials), but it is not guaranteed that the client will handle this
    251      *             properly.
    252      */
    253     @SuppressWarnings("unused")
    254     public String createDocument(String parentDocumentId, String mimeType, String displayName)
    255             throws FileNotFoundException {
    256         throw new UnsupportedOperationException("Create not supported");
    257     }
    258 
    259     /**
    260      * Rename an existing document.
    261      * <p>
    262      * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
    263      * represent the renamed document, generate and return it. Any outstanding
    264      * URI permission grants will be updated to point at the new document. If
    265      * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
    266      * rename, return {@code null}.
    267      *
    268      * @param documentId the document to rename.
    269      * @param displayName the updated display name of the document. The provider
    270      *            may alter this name to meet any internal constraints, such as
    271      *            avoiding conflicting names.
    272      * @throws AuthenticationRequiredException If authentication is required from
    273      *            the user (such as login credentials), but it is not guaranteed
    274      *            that the client will handle this properly.
    275      */
    276     @SuppressWarnings("unused")
    277     public String renameDocument(String documentId, String displayName)
    278             throws FileNotFoundException {
    279         throw new UnsupportedOperationException("Rename not supported");
    280     }
    281 
    282     /**
    283      * Delete the requested document.
    284      * <p>
    285      * Upon returning, any URI permission grants for the given document will be
    286      * revoked. If additional documents were deleted as a side effect of this
    287      * call (such as documents inside a directory) the implementor is
    288      * responsible for revoking those permissions using
    289      * {@link #revokeDocumentPermission(String)}.
    290      *
    291      * @param documentId the document to delete.
    292      * @throws AuthenticationRequiredException If authentication is required from
    293      *            the user (such as login credentials), but it is not guaranteed
    294      *            that the client will handle this properly.
    295      */
    296     @SuppressWarnings("unused")
    297     public void deleteDocument(String documentId) throws FileNotFoundException {
    298         throw new UnsupportedOperationException("Delete not supported");
    299     }
    300 
    301     /**
    302      * Copy the requested document or a document tree.
    303      * <p>
    304      * Copies a document including all child documents to another location within
    305      * the same document provider. Upon completion returns the document id of
    306      * the copied document at the target destination. {@code null} must never
    307      * be returned.
    308      *
    309      * @param sourceDocumentId the document to copy.
    310      * @param targetParentDocumentId the target document to be copied into as a child.
    311      * @throws AuthenticationRequiredException If authentication is required from
    312      *            the user (such as login credentials), but it is not guaranteed
    313      *            that the client will handle this properly.
    314      */
    315     @SuppressWarnings("unused")
    316     public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
    317             throws FileNotFoundException {
    318         throw new UnsupportedOperationException("Copy not supported");
    319     }
    320 
    321     /**
    322      * Move the requested document or a document tree.
    323      *
    324      * <p>Moves a document including all child documents to another location within
    325      * the same document provider. Upon completion returns the document id of
    326      * the copied document at the target destination. {@code null} must never
    327      * be returned.
    328      *
    329      * <p>It's the responsibility of the provider to revoke grants if the document
    330      * is no longer accessible using <code>sourceDocumentId</code>.
    331      *
    332      * @param sourceDocumentId the document to move.
    333      * @param sourceParentDocumentId the parent of the document to move.
    334      * @param targetParentDocumentId the target document to be a new parent of the
    335      *     source document.
    336      * @throws AuthenticationRequiredException If authentication is required from
    337      *            the user (such as login credentials), but it is not guaranteed
    338      *            that the client will handle this properly.
    339      */
    340     @SuppressWarnings("unused")
    341     public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
    342             String targetParentDocumentId)
    343             throws FileNotFoundException {
    344         throw new UnsupportedOperationException("Move not supported");
    345     }
    346 
    347     /**
    348      * Removes the requested document or a document tree.
    349      *
    350      * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
    351      * This method is especially useful if the document can be in multiple parents.
    352      *
    353      * <p>It's the responsibility of the provider to revoke grants if the document is
    354      * removed from the last parent, and effectively the document is deleted.
    355      *
    356      * @param documentId the document to remove.
    357      * @param parentDocumentId the parent of the document to move.
    358      * @throws AuthenticationRequiredException If authentication is required from
    359      *            the user (such as login credentials), but it is not guaranteed
    360      *            that the client will handle this properly.
    361      */
    362     @SuppressWarnings("unused")
    363     public void removeDocument(String documentId, String parentDocumentId)
    364             throws FileNotFoundException {
    365         throw new UnsupportedOperationException("Remove not supported");
    366     }
    367 
    368     /**
    369      * Finds the canonical path for the requested document. The path must start
    370      * from the parent document if parentDocumentId is not null or the root document
    371      * if parentDocumentId is null. If there are more than one path to this document,
    372      * return the most typical one. Include both the parent document or root document
    373      * and the requested document in the returned path.
    374      *
    375      * <p>This API assumes that document ID has enough info to infer the root.
    376      * Different roots should use different document ID to refer to the same
    377      * document.
    378      *
    379      *
    380      * @param parentDocumentId the document from which the path starts if not null,
    381      *     or null to indicate a path from the root is requested.
    382      * @param childDocumentId the document which path is requested.
    383      * @return the path of the requested document. If parentDocumentId is null
    384      *     returned root ID must not be null. If parentDocumentId is not null
    385      *     returned root ID must be null.
    386      * @throws AuthenticationRequiredException If authentication is required from
    387      *            the user (such as login credentials), but it is not guaranteed
    388      *            that the client will handle this properly.
    389      */
    390     public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
    391             throws FileNotFoundException {
    392         throw new UnsupportedOperationException("findDocumentPath not supported.");
    393     }
    394 
    395     /**
    396      * Creates an intent sender for a web link, if the document is web linkable.
    397      * <p>
    398      * {@link AuthenticationRequiredException} can be thrown if user does not have
    399      * sufficient permission for the linked document. Before any new permissions
    400      * are granted for the linked document, a visible UI must be shown, so the
    401      * user can explicitly confirm whether the permission grants are expected.
    402      * The user must be able to cancel the operation.
    403      * <p>
    404      * Options passed as an argument may include a list of recipients, such
    405      * as email addresses. The provider should reflect these options if possible,
    406      * but it's acceptable to ignore them. In either case, confirmation UI must
    407      * be shown before any new permission grants are granted.
    408      * <p>
    409      * It is all right to generate a web link without granting new permissions,
    410      * if opening the link would result in a page for requesting permission
    411      * access. If it's impossible then the operation must fail by throwing an exception.
    412      *
    413      * @param documentId the document to create a web link intent for.
    414      * @param options additional information, such as list of recipients. Optional.
    415      * @throws AuthenticationRequiredException If authentication is required from
    416      *            the user (such as login credentials), but it is not guaranteed
    417      *            that the client will handle this properly.
    418      *
    419      * @see DocumentsContract.Document#FLAG_WEB_LINKABLE
    420      * @see android.app.PendingIntent#getIntentSender
    421      */
    422     public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options)
    423             throws FileNotFoundException {
    424         throw new UnsupportedOperationException("createWebLink is not supported.");
    425     }
    426 
    427     /**
    428      * Return all roots currently provided. To display to users, you must define
    429      * at least one root. You should avoid making network requests to keep this
    430      * request fast.
    431      * <p>
    432      * Each root is defined by the metadata columns described in {@link Root},
    433      * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
    434      * representing a tree of documents to display under that root.
    435      * <p>
    436      * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
    437      * android.database.ContentObserver, boolean)} with
    438      * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
    439      * <p>
    440      *
    441      * @param projection list of {@link Root} columns to put into the cursor. If
    442      *            {@code null} all supported columns should be included.
    443      */
    444     public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
    445 
    446     /**
    447      * Return recently modified documents under the requested root. This will
    448      * only be called for roots that advertise
    449      * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
    450      * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
    451      * limited to only return the 64 most recently modified documents.
    452      * <p>
    453      * Recent documents do not support change notifications.
    454      *
    455      * @param projection list of {@link Document} columns to put into the
    456      *            cursor. If {@code null} all supported columns should be
    457      *            included.
    458      * @see DocumentsContract#EXTRA_LOADING
    459      */
    460     @SuppressWarnings("unused")
    461     public Cursor queryRecentDocuments(String rootId, String[] projection)
    462             throws FileNotFoundException {
    463         throw new UnsupportedOperationException("Recent not supported");
    464     }
    465 
    466     /**
    467      * Return recently modified documents under the requested root. This will
    468      * only be called for roots that advertise
    469      * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
    470      * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order of
    471      * the most recently modified documents.
    472      * <p>
    473      * If this method is overriden by the concrete DocumentsProvider and
    474      * {@link ContentResolver#QUERY_ARG_LIMIT} is specified with a nonnegative
    475      * int under queryArgs, the result will be limited by that number and
    476      * {@link ContentResolver#QUERY_ARG_LIMIT} will be specified under
    477      * {@link ContentResolver#EXTRA_HONORED_ARGS}. Otherwise, a default 64 limit
    478      * will be used and no QUERY_ARG* will be specified under
    479      * {@link ContentResolver#EXTRA_HONORED_ARGS}.
    480      * <p>
    481      * Recent documents do not support change notifications.
    482      *
    483      * @param projection list of {@link Document} columns to put into the
    484      *            cursor. If {@code null} all supported columns should be
    485      *            included.
    486      * @param queryArgs the extra query arguments.
    487      * @param signal used by the caller to signal if the request should be
    488      *            cancelled. May be null.
    489      * @see DocumentsContract#EXTRA_LOADING
    490      */
    491     @SuppressWarnings("unused")
    492     @Nullable
    493     public Cursor queryRecentDocuments(
    494             @NonNull String rootId, @Nullable String[] projection, @Nullable Bundle queryArgs,
    495             @Nullable CancellationSignal signal) throws FileNotFoundException {
    496         Preconditions.checkNotNull(rootId, "rootId can not be null");
    497 
    498         Cursor c = queryRecentDocuments(rootId, projection);
    499         Bundle extras = new Bundle();
    500         c.setExtras(extras);
    501         extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[0]);
    502         return c;
    503     }
    504 
    505     /**
    506      * Return metadata for the single requested document. You should avoid
    507      * making network requests to keep this request fast.
    508      *
    509      * @param documentId the document to return.
    510      * @param projection list of {@link Document} columns to put into the
    511      *            cursor. If {@code null} all supported columns should be
    512      *            included.
    513      * @throws AuthenticationRequiredException If authentication is required from
    514      *            the user (such as login credentials), but it is not guaranteed
    515      *            that the client will handle this properly.
    516      */
    517     public abstract Cursor queryDocument(String documentId, String[] projection)
    518             throws FileNotFoundException;
    519 
    520     /**
    521      * Return the children documents contained in the requested directory. This
    522      * must only return immediate descendants, as additional queries will be
    523      * issued to recursively explore the tree.
    524      * <p>
    525      * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
    526      * should override {@link #queryChildDocuments(String, String[], Bundle)}.
    527      * <p>
    528      * If your provider is cloud-based, and you have some data cached or pinned
    529      * locally, you may return the local data immediately, setting
    530      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
    531      * you are still fetching additional data. Then, when the network data is
    532      * available, you can send a change notification to trigger a requery and
    533      * return the complete contents. To return a Cursor with extras, you need to
    534      * extend and override {@link Cursor#getExtras()}.
    535      * <p>
    536      * To support change notifications, you must
    537      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
    538      * Uri, such as
    539      * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
    540      * you can call {@link ContentResolver#notifyChange(Uri,
    541      * android.database.ContentObserver, boolean)} with that Uri to send change
    542      * notifications.
    543      *
    544      * @param parentDocumentId the directory to return children for.
    545      * @param projection list of {@link Document} columns to put into the
    546      *            cursor. If {@code null} all supported columns should be
    547      *            included.
    548      * @param sortOrder how to order the rows, formatted as an SQL
    549      *            {@code ORDER BY} clause (excluding the ORDER BY itself).
    550      *            Passing {@code null} will use the default sort order, which
    551      *            may be unordered. This ordering is a hint that can be used to
    552      *            prioritize how data is fetched from the network, but UI may
    553      *            always enforce a specific ordering.
    554      * @throws AuthenticationRequiredException If authentication is required from
    555      *            the user (such as login credentials), but it is not guaranteed
    556      *            that the client will handle this properly.
    557      * @see DocumentsContract#EXTRA_LOADING
    558      * @see DocumentsContract#EXTRA_INFO
    559      * @see DocumentsContract#EXTRA_ERROR
    560      */
    561     public abstract Cursor queryChildDocuments(
    562             String parentDocumentId, String[] projection, String sortOrder)
    563             throws FileNotFoundException;
    564 
    565     /**
    566      * Override this method to return the children documents contained
    567      * in the requested directory. This must return immediate descendants only.
    568      *
    569      * <p>If your provider is cloud-based, and you have data cached
    570      * locally, you may return the local data immediately, setting
    571      * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that
    572      * you are still fetching additional data. Then, when the network data is
    573      * available, you can send a change notification to trigger a requery and
    574      * return the complete contents. To return a Cursor with extras, you need to
    575      * extend and override {@link Cursor#getExtras()}.
    576      *
    577      * <p>To support change notifications, you must
    578      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
    579      * Uri, such as
    580      * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
    581      * you can call {@link ContentResolver#notifyChange(Uri,
    582      * android.database.ContentObserver, boolean)} with that Uri to send change
    583      * notifications.
    584      *
    585      * @param parentDocumentId the directory to return children for.
    586      * @param projection list of {@link Document} columns to put into the
    587      *            cursor. If {@code null} all supported columns should be
    588      *            included.
    589      * @param queryArgs Bundle containing sorting information or other
    590      *            argument useful to the provider. If no sorting
    591      *            information is available, default sorting
    592      *            will be used, which may be unordered. See
    593      *            {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
    594      *            details.
    595      * @throws AuthenticationRequiredException If authentication is required from
    596      *            the user (such as login credentials), but it is not guaranteed
    597      *            that the client will handle this properly.
    598      *
    599      * @see DocumentsContract#EXTRA_LOADING
    600      * @see DocumentsContract#EXTRA_INFO
    601      * @see DocumentsContract#EXTRA_ERROR
    602      */
    603     public Cursor queryChildDocuments(
    604             String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
    605             throws FileNotFoundException {
    606 
    607         return queryChildDocuments(
    608                 parentDocumentId, projection, getSortClause(queryArgs));
    609     }
    610 
    611     /** {@hide} */
    612     @SuppressWarnings("unused")
    613     public Cursor queryChildDocumentsForManage(
    614             String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
    615             throws FileNotFoundException {
    616         throw new UnsupportedOperationException("Manage not supported");
    617     }
    618 
    619     /**
    620      * Return documents that match the given query under the requested
    621      * root. The returned documents should be sorted by relevance in descending
    622      * order. How documents are matched against the query string is an
    623      * implementation detail left to each provider, but it's suggested that at
    624      * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
    625      * case-insensitive fashion.
    626      * <p>
    627      * If your provider is cloud-based, and you have some data cached or pinned
    628      * locally, you may return the local data immediately, setting
    629      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
    630      * you are still fetching additional data. Then, when the network data is
    631      * available, you can send a change notification to trigger a requery and
    632      * return the complete contents.
    633      * <p>
    634      * To support change notifications, you must
    635      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
    636      * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
    637      * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
    638      * android.database.ContentObserver, boolean)} with that Uri to send change
    639      * notifications.
    640      *
    641      * @param rootId the root to search under.
    642      * @param query string to match documents against.
    643      * @param projection list of {@link Document} columns to put into the
    644      *            cursor. If {@code null} all supported columns should be
    645      *            included.
    646      * @throws AuthenticationRequiredException If authentication is required from
    647      *            the user (such as login credentials), but it is not guaranteed
    648      *            that the client will handle this properly.
    649      *
    650      * @see DocumentsContract#EXTRA_LOADING
    651      * @see DocumentsContract#EXTRA_INFO
    652      * @see DocumentsContract#EXTRA_ERROR
    653      */
    654     @SuppressWarnings("unused")
    655     public Cursor querySearchDocuments(String rootId, String query, String[] projection)
    656             throws FileNotFoundException {
    657         throw new UnsupportedOperationException("Search not supported");
    658     }
    659 
    660     /**
    661      * Return documents that match the given query under the requested
    662      * root. The returned documents should be sorted by relevance in descending
    663      * order. How documents are matched against the query string is an
    664      * implementation detail left to each provider, but it's suggested that at
    665      * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
    666      * case-insensitive fashion.
    667      * <p>
    668      * If your provider is cloud-based, and you have some data cached or pinned
    669      * locally, you may return the local data immediately, setting
    670      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
    671      * you are still fetching additional data. Then, when the network data is
    672      * available, you can send a change notification to trigger a requery and
    673      * return the complete contents.
    674      * <p>
    675      * To support change notifications, you must
    676      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
    677      * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
    678      * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
    679      * android.database.ContentObserver, boolean)} with that Uri to send change
    680      * notifications.
    681      *
    682      * @param rootId the root to search under.
    683      * @param projection list of {@link Document} columns to put into the
    684      *            cursor. If {@code null} all supported columns should be
    685      *            included.
    686      * @param queryArgs the query arguments.
    687      *            {@link DocumentsContract#QUERY_ARG_EXCLUDE_MEDIA},
    688      *            {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
    689      *            {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
    690      *            {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER},
    691      *            {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
    692      * @return cursor containing search result. Include
    693      *         {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
    694      *         extras {@link Bundle} when any QUERY_ARG_* value was honored
    695      *         during the preparation of the results.
    696      *
    697      * @see Root#COLUMN_QUERY_ARGS
    698      * @see ContentResolver#EXTRA_HONORED_ARGS
    699      * @see DocumentsContract#EXTRA_LOADING
    700      * @see DocumentsContract#EXTRA_INFO
    701      * @see DocumentsContract#EXTRA_ERROR
    702      */
    703     @SuppressWarnings("unused")
    704     @Nullable
    705     public Cursor querySearchDocuments(@NonNull String rootId,
    706             @Nullable String[] projection, @NonNull Bundle queryArgs) throws FileNotFoundException {
    707         Preconditions.checkNotNull(rootId, "rootId can not be null");
    708         Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
    709         return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
    710                 projection);
    711     }
    712 
    713     /**
    714      * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
    715      *
    716      * @param rootId the root to be ejected.
    717      * @see Root#FLAG_SUPPORTS_EJECT
    718      */
    719     @SuppressWarnings("unused")
    720     public void ejectRoot(String rootId) {
    721         throw new UnsupportedOperationException("Eject not supported");
    722     }
    723 
    724     /**
    725      * Returns metadata associated with the document. The type of metadata returned
    726      * is specific to the document type. For example the data returned for an image
    727      * file will likely consist primarily or solely of EXIF metadata.
    728      *
    729      * <p>The returned {@link Bundle} will contain zero or more entries depending
    730      * on the type of data supported by the document provider.
    731      *
    732      * <ol>
    733      * <li>A {@link DocumentsContract#METADATA_TYPES} containing a {@code String[]} value.
    734      *     The string array identifies the type or types of metadata returned. Each
    735      *     value in the can be used to access a {@link Bundle} of data
    736      *     containing that type of data.
    737      * <li>An entry each for each type of returned metadata. Each set of metadata is
    738      *     itself represented as a bundle and accessible via a string key naming
    739      *     the type of data.
    740      * </ol>
    741      *
    742      * @param documentId get the metadata of the document
    743      * @return a Bundle of Bundles.
    744      * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
    745      */
    746     public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
    747             throws FileNotFoundException {
    748         throw new UnsupportedOperationException("Metadata not supported");
    749     }
    750 
    751     /**
    752      * Return concrete MIME type of the requested document. Must match the value
    753      * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
    754      * implementation queries {@link #queryDocument(String, String[])}, so
    755      * providers may choose to override this as an optimization.
    756      * <p>
    757      * @throws AuthenticationRequiredException If authentication is required from
    758      *            the user (such as login credentials), but it is not guaranteed
    759      *            that the client will handle this properly.
    760      */
    761     public String getDocumentType(String documentId) throws FileNotFoundException {
    762         final Cursor cursor = queryDocument(documentId, null);
    763         try {
    764             if (cursor.moveToFirst()) {
    765                 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
    766             } else {
    767                 return null;
    768             }
    769         } finally {
    770             IoUtils.closeQuietly(cursor);
    771         }
    772     }
    773 
    774     /**
    775      * Open and return the requested document.
    776      * <p>
    777      * Your provider should return a reliable {@link ParcelFileDescriptor} to
    778      * detect when the remote caller has finished reading or writing the
    779      * document.
    780      * <p>
    781      * Mode "r" should always be supported. Provider should throw
    782      * {@link UnsupportedOperationException} if the passing mode is not supported.
    783      * You may return a pipe or socket pair if the mode is exclusively "r" or
    784      * "w", but complex modes like "rw" imply a normal file on disk that
    785      * supports seeking.
    786      * <p>
    787      * If you block while downloading content, you should periodically check
    788      * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
    789      *
    790      * @param documentId the document to return.
    791      * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
    792      * @param signal used by the caller to signal if the request should be
    793      *            cancelled. May be null.
    794      * @throws AuthenticationRequiredException If authentication is required from
    795      *            the user (such as login credentials), but it is not guaranteed
    796      *            that the client will handle this properly.
    797      * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
    798      *      OnCloseListener)
    799      * @see ParcelFileDescriptor#createReliablePipe()
    800      * @see ParcelFileDescriptor#createReliableSocketPair()
    801      * @see ParcelFileDescriptor#parseMode(String)
    802      */
    803     public abstract ParcelFileDescriptor openDocument(
    804             String documentId,
    805             String mode,
    806             @Nullable CancellationSignal signal) throws FileNotFoundException;
    807 
    808     /**
    809      * Open and return a thumbnail of the requested document.
    810      * <p>
    811      * A provider should return a thumbnail closely matching the hinted size,
    812      * attempting to serve from a local cache if possible. A provider should
    813      * never return images more than double the hinted size.
    814      * <p>
    815      * If you perform expensive operations to download or generate a thumbnail,
    816      * you should periodically check {@link CancellationSignal#isCanceled()} to
    817      * abort abandoned thumbnail requests.
    818      *
    819      * @param documentId the document to return.
    820      * @param sizeHint hint of the optimal thumbnail dimensions.
    821      * @param signal used by the caller to signal if the request should be
    822      *            cancelled. May be null.
    823      * @throws AuthenticationRequiredException If authentication is required from
    824      *            the user (such as login credentials), but it is not guaranteed
    825      *            that the client will handle this properly.
    826      * @see Document#FLAG_SUPPORTS_THUMBNAIL
    827      */
    828     @SuppressWarnings("unused")
    829     public AssetFileDescriptor openDocumentThumbnail(
    830             String documentId, Point sizeHint, CancellationSignal signal)
    831             throws FileNotFoundException {
    832         throw new UnsupportedOperationException("Thumbnails not supported");
    833     }
    834 
    835     /**
    836      * Open and return the document in a format matching the specified MIME
    837      * type filter.
    838      * <p>
    839      * A provider may perform a conversion if the documents's MIME type is not
    840      * matching the specified MIME type filter.
    841      * <p>
    842      * Virtual documents must have at least one streamable format.
    843      *
    844      * @param documentId the document to return.
    845      * @param mimeTypeFilter the MIME type filter for the requested format. May
    846      *            be *\/*, which matches any MIME type.
    847      * @param opts extra options from the client. Specific to the content
    848      *            provider.
    849      * @param signal used by the caller to signal if the request should be
    850      *            cancelled. May be null.
    851      * @throws AuthenticationRequiredException If authentication is required from
    852      *            the user (such as login credentials), but it is not guaranteed
    853      *            that the client will handle this properly.
    854      * @see #getDocumentStreamTypes(String, String)
    855      */
    856     @SuppressWarnings("unused")
    857     public AssetFileDescriptor openTypedDocument(
    858             String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
    859             throws FileNotFoundException {
    860         throw new FileNotFoundException("The requested MIME type is not supported.");
    861     }
    862 
    863     @Override
    864     public final Cursor query(Uri uri, String[] projection, String selection,
    865             String[] selectionArgs, String sortOrder) {
    866         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
    867         // transport method. We override that, and don't ever delegate to this method.
    868         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
    869     }
    870 
    871     /**
    872      * WARNING: Sub-classes should not override this method. This method is non-final
    873      * solely for the purposes of backwards compatibility.
    874      *
    875      * @see #queryChildDocuments(String, String[], Bundle),
    876      *      {@link #queryDocument(String, String[])},
    877      *      {@link #queryRecentDocuments(String, String[])},
    878      *      {@link #queryRoots(String[])}, and
    879      *      {@link #querySearchDocuments(String, String[], Bundle)}.
    880      */
    881     @Override
    882     public Cursor query(Uri uri, String[] projection, String selection,
    883             String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
    884         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
    885         // transport method. We override that, and don't ever delegate to this metohd.
    886         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
    887     }
    888 
    889     /**
    890      * Implementation is provided by the parent class. Cannot be overridden.
    891      *
    892      * @see #queryRoots(String[])
    893      * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
    894      * @see #queryDocument(String, String[])
    895      * @see #queryChildDocuments(String, String[], String)
    896      * @see #querySearchDocuments(String, String[], Bundle)
    897      */
    898     @Override
    899     public final Cursor query(
    900             Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
    901         try {
    902             switch (mMatcher.match(uri)) {
    903                 case MATCH_ROOTS:
    904                     return queryRoots(projection);
    905                 case MATCH_RECENT:
    906                     return queryRecentDocuments(
    907                             getRootId(uri), projection, queryArgs, cancellationSignal);
    908                 case MATCH_SEARCH:
    909                     return querySearchDocuments(getRootId(uri), projection, queryArgs);
    910                 case MATCH_DOCUMENT:
    911                 case MATCH_DOCUMENT_TREE:
    912                     enforceTree(uri);
    913                     return queryDocument(getDocumentId(uri), projection);
    914                 case MATCH_CHILDREN:
    915                 case MATCH_CHILDREN_TREE:
    916                     enforceTree(uri);
    917                     if (DocumentsContract.isManageMode(uri)) {
    918                         // TODO: Update "ForManage" variant to support query args.
    919                         return queryChildDocumentsForManage(
    920                                 getDocumentId(uri),
    921                                 projection,
    922                                 getSortClause(queryArgs));
    923                     } else {
    924                         return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
    925                     }
    926                 default:
    927                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
    928             }
    929         } catch (FileNotFoundException e) {
    930             Log.w(TAG, "Failed during query", e);
    931             return null;
    932         }
    933     }
    934 
    935     private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
    936         queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
    937         String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
    938 
    939         if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
    940             sortClause = ContentResolver.createSqlSortClause(queryArgs);
    941         }
    942 
    943         return sortClause;
    944     }
    945 
    946     /**
    947      * Implementation is provided by the parent class. Cannot be overridden.
    948      *
    949      * @see #getDocumentType(String)
    950      */
    951     @Override
    952     public final String getType(Uri uri) {
    953         try {
    954             switch (mMatcher.match(uri)) {
    955                 case MATCH_ROOT:
    956                     return DocumentsContract.Root.MIME_TYPE_ITEM;
    957                 case MATCH_DOCUMENT:
    958                 case MATCH_DOCUMENT_TREE:
    959                     enforceTree(uri);
    960                     return getDocumentType(getDocumentId(uri));
    961                 default:
    962                     return null;
    963             }
    964         } catch (FileNotFoundException e) {
    965             Log.w(TAG, "Failed during getType", e);
    966             return null;
    967         }
    968     }
    969 
    970     /**
    971      * Implementation is provided by the parent class. Can be overridden to
    972      * provide additional functionality, but subclasses <em>must</em> always
    973      * call the superclass. If the superclass returns {@code null}, the subclass
    974      * may implement custom behavior.
    975      * <p>
    976      * This is typically used to resolve a subtree URI into a concrete document
    977      * reference, issuing a narrower single-document URI permission grant along
    978      * the way.
    979      *
    980      * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
    981      */
    982     @CallSuper
    983     @Override
    984     public Uri canonicalize(Uri uri) {
    985         final Context context = getContext();
    986         switch (mMatcher.match(uri)) {
    987             case MATCH_DOCUMENT_TREE:
    988                 enforceTree(uri);
    989 
    990                 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
    991 
    992                 // Caller may only have prefix grant, so extend them a grant to
    993                 // the narrow URI.
    994                 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
    995                 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
    996                 return narrowUri;
    997         }
    998         return null;
    999     }
   1000 
   1001     private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
   1002         // TODO: move this to a direct AMS call
   1003         int modeFlags = 0;
   1004         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
   1005                 == PackageManager.PERMISSION_GRANTED) {
   1006             modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
   1007         }
   1008         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
   1009                 == PackageManager.PERMISSION_GRANTED) {
   1010             modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
   1011         }
   1012         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
   1013                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
   1014                 == PackageManager.PERMISSION_GRANTED) {
   1015             modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
   1016         }
   1017         return modeFlags;
   1018     }
   1019 
   1020     /**
   1021      * Implementation is provided by the parent class. Throws by default, and
   1022      * cannot be overridden.
   1023      *
   1024      * @see #createDocument(String, String, String)
   1025      */
   1026     @Override
   1027     public final Uri insert(Uri uri, ContentValues values) {
   1028         throw new UnsupportedOperationException("Insert not supported");
   1029     }
   1030 
   1031     /**
   1032      * Implementation is provided by the parent class. Throws by default, and
   1033      * cannot be overridden.
   1034      *
   1035      * @see #deleteDocument(String)
   1036      */
   1037     @Override
   1038     public final int delete(Uri uri, String selection, String[] selectionArgs) {
   1039         throw new UnsupportedOperationException("Delete not supported");
   1040     }
   1041 
   1042     /**
   1043      * Implementation is provided by the parent class. Throws by default, and
   1044      * cannot be overridden.
   1045      */
   1046     @Override
   1047     public final int update(
   1048             Uri uri, ContentValues values, String selection, String[] selectionArgs) {
   1049         throw new UnsupportedOperationException("Update not supported");
   1050     }
   1051 
   1052     /**
   1053      * Implementation is provided by the parent class. Can be overridden to
   1054      * provide additional functionality, but subclasses <em>must</em> always
   1055      * call the superclass. If the superclass returns {@code null}, the subclass
   1056      * may implement custom behavior.
   1057      */
   1058     @CallSuper
   1059     @Override
   1060     public Bundle call(String method, String arg, Bundle extras) {
   1061         if (!method.startsWith("android:")) {
   1062             // Ignore non-platform methods
   1063             return super.call(method, arg, extras);
   1064         }
   1065 
   1066         try {
   1067             return callUnchecked(method, arg, extras);
   1068         } catch (FileNotFoundException e) {
   1069             throw new ParcelableException(e);
   1070         }
   1071     }
   1072 
   1073     private Bundle callUnchecked(String method, String arg, Bundle extras)
   1074             throws FileNotFoundException {
   1075 
   1076         final Context context = getContext();
   1077         final Bundle out = new Bundle();
   1078 
   1079         if (METHOD_EJECT_ROOT.equals(method)) {
   1080             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
   1081             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
   1082             // MANAGE_DOCUMENTS or associated URI permission here instead
   1083             final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
   1084             enforceWritePermissionInner(rootUri, getCallingPackage(), null);
   1085 
   1086             final String rootId = DocumentsContract.getRootId(rootUri);
   1087             ejectRoot(rootId);
   1088 
   1089             return out;
   1090         }
   1091 
   1092         final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
   1093         final String authority = documentUri.getAuthority();
   1094         final String documentId = DocumentsContract.getDocumentId(documentUri);
   1095 
   1096         if (!mAuthority.equals(authority)) {
   1097             throw new SecurityException(
   1098                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
   1099         }
   1100 
   1101         // If the URI is a tree URI performs some validation.
   1102         enforceTree(documentUri);
   1103 
   1104         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
   1105             enforceReadPermissionInner(documentUri, getCallingPackage(), null);
   1106 
   1107             final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
   1108             final String childAuthority = childUri.getAuthority();
   1109             final String childId = DocumentsContract.getDocumentId(childUri);
   1110 
   1111             out.putBoolean(
   1112                     DocumentsContract.EXTRA_RESULT,
   1113                     mAuthority.equals(childAuthority)
   1114                             && isChildDocument(documentId, childId));
   1115 
   1116         } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
   1117             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1118 
   1119             final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
   1120             final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
   1121             final String newDocumentId = createDocument(documentId, mimeType, displayName);
   1122 
   1123             // No need to issue new grants here, since caller either has
   1124             // manage permission or a prefix grant. We might generate a
   1125             // tree style URI if that's how they called us.
   1126             final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1127                     newDocumentId);
   1128             out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1129 
   1130         } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
   1131             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1132 
   1133             final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
   1134             final IntentSender intentSender = createWebLinkIntent(documentId, options);
   1135 
   1136             out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
   1137 
   1138         } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
   1139             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1140 
   1141             final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
   1142             final String newDocumentId = renameDocument(documentId, displayName);
   1143 
   1144             if (newDocumentId != null) {
   1145                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1146                         newDocumentId);
   1147 
   1148                 // If caller came in with a narrow grant, issue them a
   1149                 // narrow grant for the newly renamed document.
   1150                 if (!isTreeUri(newDocumentUri)) {
   1151                     final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
   1152                             documentUri);
   1153                     context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
   1154                 }
   1155 
   1156                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1157 
   1158                 // Original document no longer exists, clean up any grants.
   1159                 revokeDocumentPermission(documentId);
   1160             }
   1161 
   1162         } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
   1163             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1164             deleteDocument(documentId);
   1165 
   1166             // Document no longer exists, clean up any grants.
   1167             revokeDocumentPermission(documentId);
   1168 
   1169         } else if (METHOD_COPY_DOCUMENT.equals(method)) {
   1170             final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
   1171             final String targetId = DocumentsContract.getDocumentId(targetUri);
   1172 
   1173             enforceReadPermissionInner(documentUri, getCallingPackage(), null);
   1174             enforceWritePermissionInner(targetUri, getCallingPackage(), null);
   1175 
   1176             final String newDocumentId = copyDocument(documentId, targetId);
   1177 
   1178             if (newDocumentId != null) {
   1179                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1180                         newDocumentId);
   1181 
   1182                 if (!isTreeUri(newDocumentUri)) {
   1183                     final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
   1184                             documentUri);
   1185                     context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
   1186                 }
   1187 
   1188                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1189             }
   1190 
   1191         } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
   1192             final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
   1193             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
   1194             final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
   1195             final String targetId = DocumentsContract.getDocumentId(targetUri);
   1196 
   1197             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1198             enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
   1199             enforceWritePermissionInner(targetUri, getCallingPackage(), null);
   1200 
   1201             final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
   1202 
   1203             if (newDocumentId != null) {
   1204                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1205                         newDocumentId);
   1206 
   1207                 if (!isTreeUri(newDocumentUri)) {
   1208                     final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
   1209                             documentUri);
   1210                     context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
   1211                 }
   1212 
   1213                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1214             }
   1215 
   1216         } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
   1217             final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
   1218             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
   1219 
   1220             enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
   1221             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1222             removeDocument(documentId, parentSourceId);
   1223 
   1224             // It's responsibility of the provider to revoke any grants, as the document may be
   1225             // still attached to another parents.
   1226         } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
   1227             final boolean isTreeUri = isTreeUri(documentUri);
   1228 
   1229             if (isTreeUri) {
   1230                 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
   1231             } else {
   1232                 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
   1233             }
   1234 
   1235             final String parentDocumentId = isTreeUri
   1236                     ? DocumentsContract.getTreeDocumentId(documentUri)
   1237                     : null;
   1238 
   1239             Path path = findDocumentPath(parentDocumentId, documentId);
   1240 
   1241             // Ensure provider doesn't leak information to unprivileged callers.
   1242             if (isTreeUri) {
   1243                 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
   1244                     Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
   1245                             + parentDocumentId + " found: " + path.getPath().get(0));
   1246 
   1247                     LinkedList<String> docs = new LinkedList<>(path.getPath());
   1248                     while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
   1249                         docs.removeFirst();
   1250                     }
   1251                     path = new Path(null, docs);
   1252                 }
   1253 
   1254                 if (path.getRootId() != null) {
   1255                     Log.wtf(TAG, "Provider returns root id :"
   1256                             + path.getRootId() + " unexpectedly. Erase root id.");
   1257                     path = new Path(null, path.getPath());
   1258                 }
   1259             }
   1260 
   1261             out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
   1262         } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
   1263             return getDocumentMetadata(documentId);
   1264         } else {
   1265             throw new UnsupportedOperationException("Method not supported " + method);
   1266         }
   1267 
   1268         return out;
   1269     }
   1270 
   1271     /**
   1272      * Revoke any active permission grants for the given
   1273      * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
   1274      * becomes invalid. Follows the same semantics as
   1275      * {@link Context#revokeUriPermission(Uri, int)}.
   1276      */
   1277     public final void revokeDocumentPermission(String documentId) {
   1278         final Context context = getContext();
   1279         context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
   1280         context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
   1281     }
   1282 
   1283     /**
   1284      * Implementation is provided by the parent class. Cannot be overridden.
   1285      *
   1286      * @see #openDocument(String, String, CancellationSignal)
   1287      */
   1288     @Override
   1289     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
   1290         enforceTree(uri);
   1291         return openDocument(getDocumentId(uri), mode, null);
   1292     }
   1293 
   1294     /**
   1295      * Implementation is provided by the parent class. Cannot be overridden.
   1296      *
   1297      * @see #openDocument(String, String, CancellationSignal)
   1298      */
   1299     @Override
   1300     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
   1301             throws FileNotFoundException {
   1302         enforceTree(uri);
   1303         return openDocument(getDocumentId(uri), mode, signal);
   1304     }
   1305 
   1306     /**
   1307      * Implementation is provided by the parent class. Cannot be overridden.
   1308      *
   1309      * @see #openDocument(String, String, CancellationSignal)
   1310      */
   1311     @Override
   1312     @SuppressWarnings("resource")
   1313     public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
   1314             throws FileNotFoundException {
   1315         enforceTree(uri);
   1316         final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
   1317         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
   1318     }
   1319 
   1320     /**
   1321      * Implementation is provided by the parent class. Cannot be overridden.
   1322      *
   1323      * @see #openDocument(String, String, CancellationSignal)
   1324      */
   1325     @Override
   1326     @SuppressWarnings("resource")
   1327     public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
   1328             throws FileNotFoundException {
   1329         enforceTree(uri);
   1330         final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
   1331         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
   1332     }
   1333 
   1334     /**
   1335      * Implementation is provided by the parent class. Cannot be overridden.
   1336      *
   1337      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
   1338      * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
   1339      * @see #getDocumentStreamTypes(String, String)
   1340      */
   1341     @Override
   1342     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
   1343             throws FileNotFoundException {
   1344         return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
   1345     }
   1346 
   1347     /**
   1348      * Implementation is provided by the parent class. Cannot be overridden.
   1349      *
   1350      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
   1351      * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
   1352      * @see #getDocumentStreamTypes(String, String)
   1353      */
   1354     @Override
   1355     public final AssetFileDescriptor openTypedAssetFile(
   1356             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
   1357             throws FileNotFoundException {
   1358         return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
   1359     }
   1360 
   1361     /**
   1362      * Return a list of streamable MIME types matching the filter, which can be passed to
   1363      * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
   1364      *
   1365      * <p>The default implementation returns a MIME type provided by
   1366      * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
   1367      * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
   1368      *
   1369      * <p>Virtual documents must have at least one streamable format.
   1370      *
   1371      * @see #getStreamTypes(Uri, String)
   1372      * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
   1373      */
   1374     public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
   1375         Cursor cursor = null;
   1376         try {
   1377             cursor = queryDocument(documentId, null);
   1378             if (cursor.moveToFirst()) {
   1379                 final String mimeType =
   1380                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
   1381                 final long flags =
   1382                     cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
   1383                 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
   1384                         MimeTypeFilter.matches(mimeType, mimeTypeFilter)) {
   1385                     return new String[] { mimeType };
   1386                 }
   1387             }
   1388         } catch (FileNotFoundException e) {
   1389             return null;
   1390         } finally {
   1391             IoUtils.closeQuietly(cursor);
   1392         }
   1393 
   1394         // No streamable MIME types.
   1395         return null;
   1396     }
   1397 
   1398     /**
   1399      * Called by a client to determine the types of data streams that this content provider
   1400      * support for the given URI.
   1401      *
   1402      * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
   1403      *
   1404      * @see #getDocumentStreamTypes(String, String)
   1405      */
   1406     @Override
   1407     public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
   1408         enforceTree(uri);
   1409         return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
   1410     }
   1411 
   1412     /**
   1413      * @hide
   1414      */
   1415     private final AssetFileDescriptor openTypedAssetFileImpl(
   1416             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
   1417             throws FileNotFoundException {
   1418         enforceTree(uri);
   1419         final String documentId = getDocumentId(uri);
   1420         if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
   1421             final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
   1422             return openDocumentThumbnail(documentId, sizeHint, signal);
   1423         }
   1424         if ("*/*".equals(mimeTypeFilter)) {
   1425              // If they can take anything, the untyped open call is good enough.
   1426              return openAssetFile(uri, "r");
   1427         }
   1428         final String baseType = getType(uri);
   1429         if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
   1430             // Use old untyped open call if this provider has a type for this
   1431             // URI and it matches the request.
   1432             return openAssetFile(uri, "r");
   1433         }
   1434         // For any other yet unhandled case, let the provider subclass handle it.
   1435         return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
   1436     }
   1437 }
   1438