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