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