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