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