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