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