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, @Nullable String[] tags)
    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, String mode, CancellationSignal signal) throws FileNotFoundException;
    689 
    690     /**
    691      * Open and return a thumbnail of the requested document.
    692      * <p>
    693      * A provider should return a thumbnail closely matching the hinted size,
    694      * attempting to serve from a local cache if possible. A provider should
    695      * never return images more than double the hinted size.
    696      * <p>
    697      * If you perform expensive operations to download or generate a thumbnail,
    698      * you should periodically check {@link CancellationSignal#isCanceled()} to
    699      * abort abandoned thumbnail requests.
    700      *
    701      * @param documentId the document to return.
    702      * @param sizeHint hint of the optimal thumbnail dimensions.
    703      * @param signal used by the caller to signal if the request should be
    704      *            cancelled. May be null.
    705      * @throws AuthenticationRequiredException If authentication is required from
    706      *            the user (such as login credentials), but it is not guaranteed
    707      *            that the client will handle this properly.
    708      * @see Document#FLAG_SUPPORTS_THUMBNAIL
    709      */
    710     @SuppressWarnings("unused")
    711     public AssetFileDescriptor openDocumentThumbnail(
    712             String documentId, Point sizeHint, CancellationSignal signal)
    713             throws FileNotFoundException {
    714         throw new UnsupportedOperationException("Thumbnails not supported");
    715     }
    716 
    717     /**
    718      * Open and return the document in a format matching the specified MIME
    719      * type filter.
    720      * <p>
    721      * A provider may perform a conversion if the documents's MIME type is not
    722      * matching the specified MIME type filter.
    723      * <p>
    724      * Virtual documents must have at least one streamable format.
    725      *
    726      * @param documentId the document to return.
    727      * @param mimeTypeFilter the MIME type filter for the requested format. May
    728      *            be *\/*, which matches any MIME type.
    729      * @param opts extra options from the client. Specific to the content
    730      *            provider.
    731      * @param signal used by the caller to signal if the request should be
    732      *            cancelled. May be null.
    733      * @throws AuthenticationRequiredException If authentication is required from
    734      *            the user (such as login credentials), but it is not guaranteed
    735      *            that the client will handle this properly.
    736      * @see #getDocumentStreamTypes(String, String)
    737      */
    738     @SuppressWarnings("unused")
    739     public AssetFileDescriptor openTypedDocument(
    740             String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
    741             throws FileNotFoundException {
    742         throw new FileNotFoundException("The requested MIME type is not supported.");
    743     }
    744 
    745     @Override
    746     public final Cursor query(Uri uri, String[] projection, String selection,
    747             String[] selectionArgs, String sortOrder) {
    748         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
    749         // transport method. We override that, and don't ever delegate to this method.
    750         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
    751     }
    752 
    753     /**
    754      * WARNING: Sub-classes should not override this method. This method is non-final
    755      * solely for the purposes of backwards compatibility.
    756      *
    757      * @see #queryChildDocuments(String, String[], Bundle),
    758      *      {@link #queryDocument(String, String[])},
    759      *      {@link #queryRecentDocuments(String, String[])},
    760      *      {@link #queryRoots(String[])}, and
    761      *      {@link #querySearchDocuments(String, String, String[])}.
    762      */
    763     @Override
    764     public Cursor query(Uri uri, String[] projection, String selection,
    765             String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
    766         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
    767         // transport method. We override that, and don't ever delegate to this metohd.
    768         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
    769     }
    770 
    771     /**
    772      * Implementation is provided by the parent class. Cannot be overriden.
    773      *
    774      * @see #queryRoots(String[])
    775      * @see #queryRecentDocuments(String, String[])
    776      * @see #queryDocument(String, String[])
    777      * @see #queryChildDocuments(String, String[], String)
    778      * @see #querySearchDocuments(String, String, String[])
    779      */
    780     @Override
    781     public final Cursor query(
    782             Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
    783         try {
    784             switch (mMatcher.match(uri)) {
    785                 case MATCH_ROOTS:
    786                     return queryRoots(projection);
    787                 case MATCH_RECENT:
    788                     return queryRecentDocuments(getRootId(uri), projection);
    789                 case MATCH_SEARCH:
    790                     return querySearchDocuments(
    791                             getRootId(uri), getSearchDocumentsQuery(uri), projection);
    792                 case MATCH_DOCUMENT:
    793                 case MATCH_DOCUMENT_TREE:
    794                     enforceTree(uri);
    795                     return queryDocument(getDocumentId(uri), projection);
    796                 case MATCH_CHILDREN:
    797                 case MATCH_CHILDREN_TREE:
    798                     enforceTree(uri);
    799                     if (DocumentsContract.isManageMode(uri)) {
    800                         // TODO: Update "ForManage" variant to support query args.
    801                         return queryChildDocumentsForManage(
    802                                 getDocumentId(uri),
    803                                 projection,
    804                                 getSortClause(queryArgs));
    805                     } else {
    806                         return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
    807                     }
    808                 default:
    809                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
    810             }
    811         } catch (FileNotFoundException e) {
    812             Log.w(TAG, "Failed during query", e);
    813             return null;
    814         }
    815     }
    816 
    817     private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
    818         queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
    819         String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
    820 
    821         if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
    822             sortClause = ContentResolver.createSqlSortClause(queryArgs);
    823         }
    824 
    825         return sortClause;
    826     }
    827 
    828     /**
    829      * Implementation is provided by the parent class. Cannot be overriden.
    830      *
    831      * @see #getDocumentType(String)
    832      */
    833     @Override
    834     public final String getType(Uri uri) {
    835         try {
    836             switch (mMatcher.match(uri)) {
    837                 case MATCH_ROOT:
    838                     return DocumentsContract.Root.MIME_TYPE_ITEM;
    839                 case MATCH_DOCUMENT:
    840                 case MATCH_DOCUMENT_TREE:
    841                     enforceTree(uri);
    842                     return getDocumentType(getDocumentId(uri));
    843                 default:
    844                     return null;
    845             }
    846         } catch (FileNotFoundException e) {
    847             Log.w(TAG, "Failed during getType", e);
    848             return null;
    849         }
    850     }
    851 
    852     /**
    853      * Implementation is provided by the parent class. Can be overridden to
    854      * provide additional functionality, but subclasses <em>must</em> always
    855      * call the superclass. If the superclass returns {@code null}, the subclass
    856      * may implement custom behavior.
    857      * <p>
    858      * This is typically used to resolve a subtree URI into a concrete document
    859      * reference, issuing a narrower single-document URI permission grant along
    860      * the way.
    861      *
    862      * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
    863      */
    864     @CallSuper
    865     @Override
    866     public Uri canonicalize(Uri uri) {
    867         final Context context = getContext();
    868         switch (mMatcher.match(uri)) {
    869             case MATCH_DOCUMENT_TREE:
    870                 enforceTree(uri);
    871 
    872                 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
    873 
    874                 // Caller may only have prefix grant, so extend them a grant to
    875                 // the narrow URI.
    876                 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
    877                 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
    878                 return narrowUri;
    879         }
    880         return null;
    881     }
    882 
    883     private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
    884         // TODO: move this to a direct AMS call
    885         int modeFlags = 0;
    886         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
    887                 == PackageManager.PERMISSION_GRANTED) {
    888             modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
    889         }
    890         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
    891                 == PackageManager.PERMISSION_GRANTED) {
    892             modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
    893         }
    894         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
    895                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
    896                 == PackageManager.PERMISSION_GRANTED) {
    897             modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
    898         }
    899         return modeFlags;
    900     }
    901 
    902     /**
    903      * Implementation is provided by the parent class. Throws by default, and
    904      * cannot be overriden.
    905      *
    906      * @see #createDocument(String, String, String)
    907      */
    908     @Override
    909     public final Uri insert(Uri uri, ContentValues values) {
    910         throw new UnsupportedOperationException("Insert not supported");
    911     }
    912 
    913     /**
    914      * Implementation is provided by the parent class. Throws by default, and
    915      * cannot be overriden.
    916      *
    917      * @see #deleteDocument(String)
    918      */
    919     @Override
    920     public final int delete(Uri uri, String selection, String[] selectionArgs) {
    921         throw new UnsupportedOperationException("Delete not supported");
    922     }
    923 
    924     /**
    925      * Implementation is provided by the parent class. Throws by default, and
    926      * cannot be overriden.
    927      */
    928     @Override
    929     public final int update(
    930             Uri uri, ContentValues values, String selection, String[] selectionArgs) {
    931         throw new UnsupportedOperationException("Update not supported");
    932     }
    933 
    934     /**
    935      * Implementation is provided by the parent class. Can be overridden to
    936      * provide additional functionality, but subclasses <em>must</em> always
    937      * call the superclass. If the superclass returns {@code null}, the subclass
    938      * may implement custom behavior.
    939      */
    940     @CallSuper
    941     @Override
    942     public Bundle call(String method, String arg, Bundle extras) {
    943         if (!method.startsWith("android:")) {
    944             // Ignore non-platform methods
    945             return super.call(method, arg, extras);
    946         }
    947 
    948         try {
    949             return callUnchecked(method, arg, extras);
    950         } catch (FileNotFoundException e) {
    951             throw new ParcelableException(e);
    952         }
    953     }
    954 
    955     private Bundle callUnchecked(String method, String arg, Bundle extras)
    956             throws FileNotFoundException {
    957 
    958         final Context context = getContext();
    959         final Bundle out = new Bundle();
    960 
    961         if (METHOD_EJECT_ROOT.equals(method)) {
    962             // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
    963             // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
    964             // MANAGE_DOCUMENTS or associated URI permission here instead
    965             final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
    966             enforceWritePermissionInner(rootUri, getCallingPackage(), null);
    967 
    968             final String rootId = DocumentsContract.getRootId(rootUri);
    969             ejectRoot(rootId);
    970 
    971             return out;
    972         }
    973 
    974         final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
    975         final String authority = documentUri.getAuthority();
    976         final String documentId = DocumentsContract.getDocumentId(documentUri);
    977 
    978         if (!mAuthority.equals(authority)) {
    979             throw new SecurityException(
    980                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
    981         }
    982 
    983         // If the URI is a tree URI performs some validation.
    984         enforceTree(documentUri);
    985 
    986         if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
    987             enforceReadPermissionInner(documentUri, getCallingPackage(), null);
    988 
    989             final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
    990             final String childAuthority = childUri.getAuthority();
    991             final String childId = DocumentsContract.getDocumentId(childUri);
    992 
    993             out.putBoolean(
    994                     DocumentsContract.EXTRA_RESULT,
    995                     mAuthority.equals(childAuthority)
    996                             && isChildDocument(documentId, childId));
    997 
    998         } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
    999             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1000 
   1001             final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
   1002             final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
   1003             final String newDocumentId = createDocument(documentId, mimeType, displayName);
   1004 
   1005             // No need to issue new grants here, since caller either has
   1006             // manage permission or a prefix grant. We might generate a
   1007             // tree style URI if that's how they called us.
   1008             final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1009                     newDocumentId);
   1010             out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1011 
   1012         } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
   1013             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1014 
   1015             final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
   1016             final IntentSender intentSender = createWebLinkIntent(documentId, options);
   1017 
   1018             out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
   1019 
   1020         } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
   1021             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1022 
   1023             final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
   1024             final String newDocumentId = renameDocument(documentId, displayName);
   1025 
   1026             if (newDocumentId != null) {
   1027                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1028                         newDocumentId);
   1029 
   1030                 // If caller came in with a narrow grant, issue them a
   1031                 // narrow grant for the newly renamed document.
   1032                 if (!isTreeUri(newDocumentUri)) {
   1033                     final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
   1034                             documentUri);
   1035                     context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
   1036                 }
   1037 
   1038                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1039 
   1040                 // Original document no longer exists, clean up any grants.
   1041                 revokeDocumentPermission(documentId);
   1042             }
   1043 
   1044         } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
   1045             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1046             deleteDocument(documentId);
   1047 
   1048             // Document no longer exists, clean up any grants.
   1049             revokeDocumentPermission(documentId);
   1050 
   1051         } else if (METHOD_COPY_DOCUMENT.equals(method)) {
   1052             final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
   1053             final String targetId = DocumentsContract.getDocumentId(targetUri);
   1054 
   1055             enforceReadPermissionInner(documentUri, getCallingPackage(), null);
   1056             enforceWritePermissionInner(targetUri, getCallingPackage(), null);
   1057 
   1058             final String newDocumentId = copyDocument(documentId, targetId);
   1059 
   1060             if (newDocumentId != null) {
   1061                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1062                         newDocumentId);
   1063 
   1064                 if (!isTreeUri(newDocumentUri)) {
   1065                     final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
   1066                             documentUri);
   1067                     context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
   1068                 }
   1069 
   1070                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1071             }
   1072 
   1073         } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
   1074             final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
   1075             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
   1076             final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
   1077             final String targetId = DocumentsContract.getDocumentId(targetUri);
   1078 
   1079             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1080             enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
   1081             enforceWritePermissionInner(targetUri, getCallingPackage(), null);
   1082 
   1083             final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
   1084 
   1085             if (newDocumentId != null) {
   1086                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
   1087                         newDocumentId);
   1088 
   1089                 if (!isTreeUri(newDocumentUri)) {
   1090                     final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
   1091                             documentUri);
   1092                     context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
   1093                 }
   1094 
   1095                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
   1096             }
   1097 
   1098         } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
   1099             final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
   1100             final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
   1101 
   1102             enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
   1103             enforceWritePermissionInner(documentUri, getCallingPackage(), null);
   1104             removeDocument(documentId, parentSourceId);
   1105 
   1106             // It's responsibility of the provider to revoke any grants, as the document may be
   1107             // still attached to another parents.
   1108         } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
   1109             final boolean isTreeUri = isTreeUri(documentUri);
   1110 
   1111             if (isTreeUri) {
   1112                 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
   1113             } else {
   1114                 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
   1115             }
   1116 
   1117             final String parentDocumentId = isTreeUri
   1118                     ? DocumentsContract.getTreeDocumentId(documentUri)
   1119                     : null;
   1120 
   1121             Path path = findDocumentPath(parentDocumentId, documentId);
   1122 
   1123             // Ensure provider doesn't leak information to unprivileged callers.
   1124             if (isTreeUri) {
   1125                 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
   1126                     Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
   1127                             + parentDocumentId + " found: " + path.getPath().get(0));
   1128 
   1129                     LinkedList<String> docs = new LinkedList<>(path.getPath());
   1130                     while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
   1131                         docs.removeFirst();
   1132                     }
   1133                     path = new Path(null, docs);
   1134                 }
   1135 
   1136                 if (path.getRootId() != null) {
   1137                     Log.wtf(TAG, "Provider returns root id :"
   1138                             + path.getRootId() + " unexpectedly. Erase root id.");
   1139                     path = new Path(null, path.getPath());
   1140                 }
   1141             }
   1142 
   1143             out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
   1144         } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
   1145             return getDocumentMetadata(
   1146                     documentId, extras.getStringArray(DocumentsContract.EXTRA_METADATA_TAGS));
   1147         } else {
   1148             throw new UnsupportedOperationException("Method not supported " + method);
   1149         }
   1150 
   1151         return out;
   1152     }
   1153 
   1154     /**
   1155      * Revoke any active permission grants for the given
   1156      * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
   1157      * becomes invalid. Follows the same semantics as
   1158      * {@link Context#revokeUriPermission(Uri, int)}.
   1159      */
   1160     public final void revokeDocumentPermission(String documentId) {
   1161         final Context context = getContext();
   1162         context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
   1163         context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
   1164     }
   1165 
   1166     /**
   1167      * Implementation is provided by the parent class. Cannot be overriden.
   1168      *
   1169      * @see #openDocument(String, String, CancellationSignal)
   1170      */
   1171     @Override
   1172     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
   1173         enforceTree(uri);
   1174         return openDocument(getDocumentId(uri), mode, null);
   1175     }
   1176 
   1177     /**
   1178      * Implementation is provided by the parent class. Cannot be overriden.
   1179      *
   1180      * @see #openDocument(String, String, CancellationSignal)
   1181      */
   1182     @Override
   1183     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
   1184             throws FileNotFoundException {
   1185         enforceTree(uri);
   1186         return openDocument(getDocumentId(uri), mode, signal);
   1187     }
   1188 
   1189     /**
   1190      * Implementation is provided by the parent class. Cannot be overriden.
   1191      *
   1192      * @see #openDocument(String, String, CancellationSignal)
   1193      */
   1194     @Override
   1195     @SuppressWarnings("resource")
   1196     public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
   1197             throws FileNotFoundException {
   1198         enforceTree(uri);
   1199         final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
   1200         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
   1201     }
   1202 
   1203     /**
   1204      * Implementation is provided by the parent class. Cannot be overriden.
   1205      *
   1206      * @see #openDocument(String, String, CancellationSignal)
   1207      */
   1208     @Override
   1209     @SuppressWarnings("resource")
   1210     public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
   1211             throws FileNotFoundException {
   1212         enforceTree(uri);
   1213         final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
   1214         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
   1215     }
   1216 
   1217     /**
   1218      * Implementation is provided by the parent class. Cannot be overriden.
   1219      *
   1220      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
   1221      * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
   1222      * @see #getDocumentStreamTypes(String, String)
   1223      */
   1224     @Override
   1225     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
   1226             throws FileNotFoundException {
   1227         return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
   1228     }
   1229 
   1230     /**
   1231      * Implementation is provided by the parent class. Cannot be overriden.
   1232      *
   1233      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
   1234      * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
   1235      * @see #getDocumentStreamTypes(String, String)
   1236      */
   1237     @Override
   1238     public final AssetFileDescriptor openTypedAssetFile(
   1239             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
   1240             throws FileNotFoundException {
   1241         return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
   1242     }
   1243 
   1244     /**
   1245      * Return a list of streamable MIME types matching the filter, which can be passed to
   1246      * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
   1247      *
   1248      * <p>The default implementation returns a MIME type provided by
   1249      * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
   1250      * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
   1251      *
   1252      * <p>Virtual documents must have at least one streamable format.
   1253      *
   1254      * @see #getStreamTypes(Uri, String)
   1255      * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
   1256      */
   1257     public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
   1258         Cursor cursor = null;
   1259         try {
   1260             cursor = queryDocument(documentId, null);
   1261             if (cursor.moveToFirst()) {
   1262                 final String mimeType =
   1263                     cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
   1264                 final long flags =
   1265                     cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
   1266                 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
   1267                         mimeTypeMatches(mimeTypeFilter, mimeType)) {
   1268                     return new String[] { mimeType };
   1269                 }
   1270             }
   1271         } catch (FileNotFoundException e) {
   1272             return null;
   1273         } finally {
   1274             IoUtils.closeQuietly(cursor);
   1275         }
   1276 
   1277         // No streamable MIME types.
   1278         return null;
   1279     }
   1280 
   1281     /**
   1282      * Called by a client to determine the types of data streams that this content provider
   1283      * support for the given URI.
   1284      *
   1285      * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
   1286      *
   1287      * @see #getDocumentStreamTypes(String, String)
   1288      */
   1289     @Override
   1290     public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
   1291         enforceTree(uri);
   1292         return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
   1293     }
   1294 
   1295     /**
   1296      * @hide
   1297      */
   1298     private final AssetFileDescriptor openTypedAssetFileImpl(
   1299             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
   1300             throws FileNotFoundException {
   1301         enforceTree(uri);
   1302         final String documentId = getDocumentId(uri);
   1303         if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
   1304             final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
   1305             return openDocumentThumbnail(documentId, sizeHint, signal);
   1306         }
   1307         if ("*/*".equals(mimeTypeFilter)) {
   1308              // If they can take anything, the untyped open call is good enough.
   1309              return openAssetFile(uri, "r");
   1310         }
   1311         final String baseType = getType(uri);
   1312         if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
   1313             // Use old untyped open call if this provider has a type for this
   1314             // URI and it matches the request.
   1315             return openAssetFile(uri, "r");
   1316         }
   1317         // For any other yet unhandled case, let the provider subclass handle it.
   1318         return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
   1319     }
   1320 
   1321     /**
   1322      * @hide
   1323      */
   1324     public static boolean mimeTypeMatches(String filter, String test) {
   1325         if (test == null) {
   1326             return false;
   1327         } else if (filter == null || "*/*".equals(filter)) {
   1328             return true;
   1329         } else if (filter.equals(test)) {
   1330             return true;
   1331         } else if (filter.endsWith("/*")) {
   1332             return filter.regionMatches(0, test, 0, filter.indexOf('/'));
   1333         } else {
   1334             return false;
   1335         }
   1336     }
   1337 }
   1338