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