• 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_CREATE_DOCUMENT;
20 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
21 import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
22 import static android.provider.DocumentsContract.buildDocumentUri;
23 import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
24 import static android.provider.DocumentsContract.buildTreeDocumentUri;
25 import static android.provider.DocumentsContract.getDocumentId;
26 import static android.provider.DocumentsContract.getRootId;
27 import static android.provider.DocumentsContract.getSearchDocumentsQuery;
28 import static android.provider.DocumentsContract.getTreeDocumentId;
29 import static android.provider.DocumentsContract.isTreeUri;
30 
31 import android.annotation.CallSuper;
32 import android.content.ContentProvider;
33 import android.content.ContentResolver;
34 import android.content.ContentValues;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.UriMatcher;
38 import android.content.pm.PackageManager;
39 import android.content.pm.ProviderInfo;
40 import android.content.res.AssetFileDescriptor;
41 import android.database.Cursor;
42 import android.graphics.Point;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.os.CancellationSignal;
46 import android.os.ParcelFileDescriptor;
47 import android.os.ParcelFileDescriptor.OnCloseListener;
48 import android.provider.DocumentsContract.Document;
49 import android.provider.DocumentsContract.Root;
50 import android.util.Log;
51 
52 import libcore.io.IoUtils;
53 
54 import java.io.FileNotFoundException;
55 import java.util.Objects;
56 
57 /**
58  * Base class for a document provider. A document provider offers read and write
59  * access to durable files, such as files stored on a local disk, or files in a
60  * cloud storage service. To create a document provider, extend this class,
61  * implement the abstract methods, and add it to your manifest like this:
62  *
63  * <pre class="prettyprint">&lt;manifest&gt;
64  *    ...
65  *    &lt;application&gt;
66  *        ...
67  *        &lt;provider
68  *            android:name="com.example.MyCloudProvider"
69  *            android:authorities="com.example.mycloudprovider"
70  *            android:exported="true"
71  *            android:grantUriPermissions="true"
72  *            android:permission="android.permission.MANAGE_DOCUMENTS"
73  *            android:enabled="@bool/isAtLeastKitKat"&gt;
74  *            &lt;intent-filter&gt;
75  *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
76  *            &lt;/intent-filter&gt;
77  *        &lt;/provider&gt;
78  *        ...
79  *    &lt;/application&gt;
80  *&lt;/manifest&gt;</pre>
81  * <p>
82  * When defining your provider, you must protect it with
83  * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
84  * only the system can obtain. Applications cannot use a documents provider
85  * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
86  * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
87  * navigate and select documents. When a user selects documents through that UI,
88  * the system issues narrow URI permission grants to the requesting application.
89  * </p>
90  * <h3>Documents</h3>
91  * <p>
92  * A document can be either an openable stream (with a specific MIME type), or a
93  * directory containing additional documents (with the
94  * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
95  * of a subtree containing zero or more documents, which can recursively contain
96  * even more documents and directories.
97  * </p>
98  * <p>
99  * Each document can have different capabilities, as described by
100  * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
101  * as a thumbnail, your provider can set
102  * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
103  * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
104  * that thumbnail.
105  * </p>
106  * <p>
107  * Each document under a provider is uniquely referenced by its
108  * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
109  * single document can be included in multiple directories when responding to
110  * {@link #queryChildDocuments(String, String[], String)}. For example, a
111  * provider might surface a single photo in multiple locations: once in a
112  * directory of geographic locations, and again in a directory of dates.
113  * </p>
114  * <h3>Roots</h3>
115  * <p>
116  * All documents are surfaced through one or more "roots." Each root represents
117  * the top of a document tree that a user can navigate. For example, a root
118  * could represent an account or a physical storage device. Similar to
119  * documents, each root can have capabilities expressed through
120  * {@link Root#COLUMN_FLAGS}.
121  * </p>
122  *
123  * @see Intent#ACTION_OPEN_DOCUMENT
124  * @see Intent#ACTION_OPEN_DOCUMENT_TREE
125  * @see Intent#ACTION_CREATE_DOCUMENT
126  */
127 public abstract class DocumentsProvider extends ContentProvider {
128     private static final String TAG = "DocumentsProvider";
129 
130     private static final int MATCH_ROOTS = 1;
131     private static final int MATCH_ROOT = 2;
132     private static final int MATCH_RECENT = 3;
133     private static final int MATCH_SEARCH = 4;
134     private static final int MATCH_DOCUMENT = 5;
135     private static final int MATCH_CHILDREN = 6;
136     private static final int MATCH_DOCUMENT_TREE = 7;
137     private static final int MATCH_CHILDREN_TREE = 8;
138 
139     private String mAuthority;
140 
141     private UriMatcher mMatcher;
142 
143     /**
144      * Implementation is provided by the parent class.
145      */
146     @Override
attachInfo(Context context, ProviderInfo info)147     public void attachInfo(Context context, ProviderInfo info) {
148         mAuthority = info.authority;
149 
150         mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
151         mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
152         mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
153         mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
154         mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
155         mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
156         mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
157         mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
158         mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
159 
160         // Sanity check our setup
161         if (!info.exported) {
162             throw new SecurityException("Provider must be exported");
163         }
164         if (!info.grantUriPermissions) {
165             throw new SecurityException("Provider must grantUriPermissions");
166         }
167         if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
168                 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
169             throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
170         }
171 
172         super.attachInfo(context, info);
173     }
174 
175     /**
176      * Test if a document is descendant (child, grandchild, etc) from the given
177      * parent. For example, providers must implement this to support
178      * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
179      * requests to keep this request fast.
180      *
181      * @param parentDocumentId parent to verify against.
182      * @param documentId child to verify.
183      * @return if given document is a descendant of the given parent.
184      * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
185      */
isChildDocument(String parentDocumentId, String documentId)186     public boolean isChildDocument(String parentDocumentId, String documentId) {
187         return false;
188     }
189 
190     /** {@hide} */
enforceTree(Uri documentUri)191     private void enforceTree(Uri documentUri) {
192         if (isTreeUri(documentUri)) {
193             final String parent = getTreeDocumentId(documentUri);
194             final String child = getDocumentId(documentUri);
195             if (Objects.equals(parent, child)) {
196                 return;
197             }
198             if (!isChildDocument(parent, child)) {
199                 throw new SecurityException(
200                         "Document " + child + " is not a descendant of " + parent);
201             }
202         }
203     }
204 
205     /**
206      * Create a new document and return its newly generated
207      * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
208      * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
209      * not change once returned.
210      *
211      * @param parentDocumentId the parent directory to create the new document
212      *            under.
213      * @param mimeType the concrete MIME type associated with the new document.
214      *            If the MIME type is not supported, the provider must throw.
215      * @param displayName the display name of the new document. The provider may
216      *            alter this name to meet any internal constraints, such as
217      *            avoiding conflicting names.
218      */
219     @SuppressWarnings("unused")
createDocument(String parentDocumentId, String mimeType, String displayName)220     public String createDocument(String parentDocumentId, String mimeType, String displayName)
221             throws FileNotFoundException {
222         throw new UnsupportedOperationException("Create not supported");
223     }
224 
225     /**
226      * Rename an existing document.
227      * <p>
228      * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
229      * represent the renamed document, generate and return it. Any outstanding
230      * URI permission grants will be updated to point at the new document. If
231      * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
232      * rename, return {@code null}.
233      *
234      * @param documentId the document to rename.
235      * @param displayName the updated display name of the document. The provider
236      *            may alter this name to meet any internal constraints, such as
237      *            avoiding conflicting names.
238      */
239     @SuppressWarnings("unused")
renameDocument(String documentId, String displayName)240     public String renameDocument(String documentId, String displayName)
241             throws FileNotFoundException {
242         throw new UnsupportedOperationException("Rename not supported");
243     }
244 
245     /**
246      * Delete the requested document.
247      * <p>
248      * Upon returning, any URI permission grants for the given document will be
249      * revoked. If additional documents were deleted as a side effect of this
250      * call (such as documents inside a directory) the implementor is
251      * responsible for revoking those permissions using
252      * {@link #revokeDocumentPermission(String)}.
253      *
254      * @param documentId the document to delete.
255      */
256     @SuppressWarnings("unused")
deleteDocument(String documentId)257     public void deleteDocument(String documentId) throws FileNotFoundException {
258         throw new UnsupportedOperationException("Delete not supported");
259     }
260 
261     /**
262      * Return all roots currently provided. To display to users, you must define
263      * at least one root. You should avoid making network requests to keep this
264      * request fast.
265      * <p>
266      * Each root is defined by the metadata columns described in {@link Root},
267      * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
268      * representing a tree of documents to display under that root.
269      * <p>
270      * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
271      * android.database.ContentObserver, boolean)} with
272      * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
273      *
274      * @param projection list of {@link Root} columns to put into the cursor. If
275      *            {@code null} all supported columns should be included.
276      */
queryRoots(String[] projection)277     public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
278 
279     /**
280      * Return recently modified documents under the requested root. This will
281      * only be called for roots that advertise
282      * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
283      * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
284      * limited to only return the 64 most recently modified documents.
285      * <p>
286      * Recent documents do not support change notifications.
287      *
288      * @param projection list of {@link Document} columns to put into the
289      *            cursor. If {@code null} all supported columns should be
290      *            included.
291      * @see DocumentsContract#EXTRA_LOADING
292      */
293     @SuppressWarnings("unused")
queryRecentDocuments(String rootId, String[] projection)294     public Cursor queryRecentDocuments(String rootId, String[] projection)
295             throws FileNotFoundException {
296         throw new UnsupportedOperationException("Recent not supported");
297     }
298 
299     /**
300      * Return metadata for the single requested document. You should avoid
301      * making network requests to keep this request fast.
302      *
303      * @param documentId the document to return.
304      * @param projection list of {@link Document} columns to put into the
305      *            cursor. If {@code null} all supported columns should be
306      *            included.
307      */
queryDocument(String documentId, String[] projection)308     public abstract Cursor queryDocument(String documentId, String[] projection)
309             throws FileNotFoundException;
310 
311     /**
312      * Return the children documents contained in the requested directory. This
313      * must only return immediate descendants, as additional queries will be
314      * issued to recursively explore the tree.
315      * <p>
316      * If your provider is cloud-based, and you have some data cached or pinned
317      * locally, you may return the local data immediately, setting
318      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
319      * you are still fetching additional data. Then, when the network data is
320      * available, you can send a change notification to trigger a requery and
321      * return the complete contents. To return a Cursor with extras, you need to
322      * extend and override {@link Cursor#getExtras()}.
323      * <p>
324      * To support change notifications, you must
325      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
326      * Uri, such as
327      * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
328      * you can call {@link ContentResolver#notifyChange(Uri,
329      * android.database.ContentObserver, boolean)} with that Uri to send change
330      * notifications.
331      *
332      * @param parentDocumentId the directory to return children for.
333      * @param projection list of {@link Document} columns to put into the
334      *            cursor. If {@code null} all supported columns should be
335      *            included.
336      * @param sortOrder how to order the rows, formatted as an SQL
337      *            {@code ORDER BY} clause (excluding the ORDER BY itself).
338      *            Passing {@code null} will use the default sort order, which
339      *            may be unordered. This ordering is a hint that can be used to
340      *            prioritize how data is fetched from the network, but UI may
341      *            always enforce a specific ordering.
342      * @see DocumentsContract#EXTRA_LOADING
343      * @see DocumentsContract#EXTRA_INFO
344      * @see DocumentsContract#EXTRA_ERROR
345      */
queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)346     public abstract Cursor queryChildDocuments(
347             String parentDocumentId, String[] projection, String sortOrder)
348             throws FileNotFoundException;
349 
350     /** {@hide} */
351     @SuppressWarnings("unused")
queryChildDocumentsForManage( String parentDocumentId, String[] projection, String sortOrder)352     public Cursor queryChildDocumentsForManage(
353             String parentDocumentId, String[] projection, String sortOrder)
354             throws FileNotFoundException {
355         throw new UnsupportedOperationException("Manage not supported");
356     }
357 
358     /**
359      * Return documents that match the given query under the requested
360      * root. The returned documents should be sorted by relevance in descending
361      * order. How documents are matched against the query string is an
362      * implementation detail left to each provider, but it's suggested that at
363      * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
364      * case-insensitive fashion.
365      * <p>
366      * Only documents may be returned; directories are not supported in search
367      * results.
368      * <p>
369      * If your provider is cloud-based, and you have some data cached or pinned
370      * locally, you may return the local data immediately, setting
371      * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
372      * you are still fetching additional data. Then, when the network data is
373      * available, you can send a change notification to trigger a requery and
374      * return the complete contents.
375      * <p>
376      * To support change notifications, you must
377      * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
378      * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
379      * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
380      * android.database.ContentObserver, boolean)} with that Uri to send change
381      * notifications.
382      *
383      * @param rootId the root to search under.
384      * @param query string to match documents against.
385      * @param projection list of {@link Document} columns to put into the
386      *            cursor. If {@code null} all supported columns should be
387      *            included.
388      * @see DocumentsContract#EXTRA_LOADING
389      * @see DocumentsContract#EXTRA_INFO
390      * @see DocumentsContract#EXTRA_ERROR
391      */
392     @SuppressWarnings("unused")
querySearchDocuments(String rootId, String query, String[] projection)393     public Cursor querySearchDocuments(String rootId, String query, String[] projection)
394             throws FileNotFoundException {
395         throw new UnsupportedOperationException("Search not supported");
396     }
397 
398     /**
399      * Return concrete MIME type of the requested document. Must match the value
400      * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
401      * implementation queries {@link #queryDocument(String, String[])}, so
402      * providers may choose to override this as an optimization.
403      */
getDocumentType(String documentId)404     public String getDocumentType(String documentId) throws FileNotFoundException {
405         final Cursor cursor = queryDocument(documentId, null);
406         try {
407             if (cursor.moveToFirst()) {
408                 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
409             } else {
410                 return null;
411             }
412         } finally {
413             IoUtils.closeQuietly(cursor);
414         }
415     }
416 
417     /**
418      * Open and return the requested document.
419      * <p>
420      * Your provider should return a reliable {@link ParcelFileDescriptor} to
421      * detect when the remote caller has finished reading or writing the
422      * document. You may return a pipe or socket pair if the mode is exclusively
423      * "r" or "w", but complex modes like "rw" imply a normal file on disk that
424      * supports seeking.
425      * <p>
426      * If you block while downloading content, you should periodically check
427      * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
428      *
429      * @param documentId the document to return.
430      * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
431      * @param signal used by the caller to signal if the request should be
432      *            cancelled. May be null.
433      * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
434      *      OnCloseListener)
435      * @see ParcelFileDescriptor#createReliablePipe()
436      * @see ParcelFileDescriptor#createReliableSocketPair()
437      * @see ParcelFileDescriptor#parseMode(String)
438      */
openDocument( String documentId, String mode, CancellationSignal signal)439     public abstract ParcelFileDescriptor openDocument(
440             String documentId, String mode, CancellationSignal signal) throws FileNotFoundException;
441 
442     /**
443      * Open and return a thumbnail of the requested document.
444      * <p>
445      * A provider should return a thumbnail closely matching the hinted size,
446      * attempting to serve from a local cache if possible. A provider should
447      * never return images more than double the hinted size.
448      * <p>
449      * If you perform expensive operations to download or generate a thumbnail,
450      * you should periodically check {@link CancellationSignal#isCanceled()} to
451      * abort abandoned thumbnail requests.
452      *
453      * @param documentId the document to return.
454      * @param sizeHint hint of the optimal thumbnail dimensions.
455      * @param signal used by the caller to signal if the request should be
456      *            cancelled. May be null.
457      * @see Document#FLAG_SUPPORTS_THUMBNAIL
458      */
459     @SuppressWarnings("unused")
openDocumentThumbnail( String documentId, Point sizeHint, CancellationSignal signal)460     public AssetFileDescriptor openDocumentThumbnail(
461             String documentId, Point sizeHint, CancellationSignal signal)
462             throws FileNotFoundException {
463         throw new UnsupportedOperationException("Thumbnails not supported");
464     }
465 
466     /**
467      * Implementation is provided by the parent class. Cannot be overriden.
468      *
469      * @see #queryRoots(String[])
470      * @see #queryRecentDocuments(String, String[])
471      * @see #queryDocument(String, String[])
472      * @see #queryChildDocuments(String, String[], String)
473      * @see #querySearchDocuments(String, String, String[])
474      */
475     @Override
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)476     public final Cursor query(Uri uri, String[] projection, String selection,
477             String[] selectionArgs, String sortOrder) {
478         try {
479             switch (mMatcher.match(uri)) {
480                 case MATCH_ROOTS:
481                     return queryRoots(projection);
482                 case MATCH_RECENT:
483                     return queryRecentDocuments(getRootId(uri), projection);
484                 case MATCH_SEARCH:
485                     return querySearchDocuments(
486                             getRootId(uri), getSearchDocumentsQuery(uri), projection);
487                 case MATCH_DOCUMENT:
488                 case MATCH_DOCUMENT_TREE:
489                     enforceTree(uri);
490                     return queryDocument(getDocumentId(uri), projection);
491                 case MATCH_CHILDREN:
492                 case MATCH_CHILDREN_TREE:
493                     enforceTree(uri);
494                     if (DocumentsContract.isManageMode(uri)) {
495                         return queryChildDocumentsForManage(
496                                 getDocumentId(uri), projection, sortOrder);
497                     } else {
498                         return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
499                     }
500                 default:
501                     throw new UnsupportedOperationException("Unsupported Uri " + uri);
502             }
503         } catch (FileNotFoundException e) {
504             Log.w(TAG, "Failed during query", e);
505             return null;
506         }
507     }
508 
509     /**
510      * Implementation is provided by the parent class. Cannot be overriden.
511      *
512      * @see #getDocumentType(String)
513      */
514     @Override
getType(Uri uri)515     public final String getType(Uri uri) {
516         try {
517             switch (mMatcher.match(uri)) {
518                 case MATCH_ROOT:
519                     return DocumentsContract.Root.MIME_TYPE_ITEM;
520                 case MATCH_DOCUMENT:
521                 case MATCH_DOCUMENT_TREE:
522                     enforceTree(uri);
523                     return getDocumentType(getDocumentId(uri));
524                 default:
525                     return null;
526             }
527         } catch (FileNotFoundException e) {
528             Log.w(TAG, "Failed during getType", e);
529             return null;
530         }
531     }
532 
533     /**
534      * Implementation is provided by the parent class. Can be overridden to
535      * provide additional functionality, but subclasses <em>must</em> always
536      * call the superclass. If the superclass returns {@code null}, the subclass
537      * may implement custom behavior.
538      * <p>
539      * This is typically used to resolve a subtree URI into a concrete document
540      * reference, issuing a narrower single-document URI permission grant along
541      * the way.
542      *
543      * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
544      */
545     @CallSuper
546     @Override
canonicalize(Uri uri)547     public Uri canonicalize(Uri uri) {
548         final Context context = getContext();
549         switch (mMatcher.match(uri)) {
550             case MATCH_DOCUMENT_TREE:
551                 enforceTree(uri);
552 
553                 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
554 
555                 // Caller may only have prefix grant, so extend them a grant to
556                 // the narrow URI.
557                 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
558                 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
559                 return narrowUri;
560         }
561         return null;
562     }
563 
getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri)564     private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
565         // TODO: move this to a direct AMS call
566         int modeFlags = 0;
567         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
568                 == PackageManager.PERMISSION_GRANTED) {
569             modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
570         }
571         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
572                 == PackageManager.PERMISSION_GRANTED) {
573             modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
574         }
575         if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
576                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
577                 == PackageManager.PERMISSION_GRANTED) {
578             modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
579         }
580         return modeFlags;
581     }
582 
583     /**
584      * Implementation is provided by the parent class. Throws by default, and
585      * cannot be overriden.
586      *
587      * @see #createDocument(String, String, String)
588      */
589     @Override
insert(Uri uri, ContentValues values)590     public final Uri insert(Uri uri, ContentValues values) {
591         throw new UnsupportedOperationException("Insert not supported");
592     }
593 
594     /**
595      * Implementation is provided by the parent class. Throws by default, and
596      * cannot be overriden.
597      *
598      * @see #deleteDocument(String)
599      */
600     @Override
delete(Uri uri, String selection, String[] selectionArgs)601     public final int delete(Uri uri, String selection, String[] selectionArgs) {
602         throw new UnsupportedOperationException("Delete not supported");
603     }
604 
605     /**
606      * Implementation is provided by the parent class. Throws by default, and
607      * cannot be overriden.
608      */
609     @Override
update( Uri uri, ContentValues values, String selection, String[] selectionArgs)610     public final int update(
611             Uri uri, ContentValues values, String selection, String[] selectionArgs) {
612         throw new UnsupportedOperationException("Update not supported");
613     }
614 
615     /**
616      * Implementation is provided by the parent class. Can be overridden to
617      * provide additional functionality, but subclasses <em>must</em> always
618      * call the superclass. If the superclass returns {@code null}, the subclass
619      * may implement custom behavior.
620      */
621     @CallSuper
622     @Override
call(String method, String arg, Bundle extras)623     public Bundle call(String method, String arg, Bundle extras) {
624         if (!method.startsWith("android:")) {
625             // Ignore non-platform methods
626             return super.call(method, arg, extras);
627         }
628 
629         final Context context = getContext();
630         final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
631         final String authority = documentUri.getAuthority();
632         final String documentId = DocumentsContract.getDocumentId(documentUri);
633 
634         if (!mAuthority.equals(authority)) {
635             throw new SecurityException(
636                     "Requested authority " + authority + " doesn't match provider " + mAuthority);
637         }
638         enforceTree(documentUri);
639 
640         final Bundle out = new Bundle();
641         try {
642             if (METHOD_CREATE_DOCUMENT.equals(method)) {
643                 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
644 
645                 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
646                 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
647                 final String newDocumentId = createDocument(documentId, mimeType, displayName);
648 
649                 // No need to issue new grants here, since caller either has
650                 // manage permission or a prefix grant. We might generate a
651                 // tree style URI if that's how they called us.
652                 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
653                         newDocumentId);
654                 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
655 
656             } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
657                 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
658 
659                 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
660                 final String newDocumentId = renameDocument(documentId, displayName);
661 
662                 if (newDocumentId != null) {
663                     final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
664                             newDocumentId);
665 
666                     // If caller came in with a narrow grant, issue them a
667                     // narrow grant for the newly renamed document.
668                     if (!isTreeUri(newDocumentUri)) {
669                         final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
670                                 documentUri);
671                         context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
672                     }
673 
674                     out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
675 
676                     // Original document no longer exists, clean up any grants
677                     revokeDocumentPermission(documentId);
678                 }
679 
680             } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
681                 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
682                 deleteDocument(documentId);
683 
684                 // Document no longer exists, clean up any grants
685                 revokeDocumentPermission(documentId);
686 
687             } else {
688                 throw new UnsupportedOperationException("Method not supported " + method);
689             }
690         } catch (FileNotFoundException e) {
691             throw new IllegalStateException("Failed call " + method, e);
692         }
693         return out;
694     }
695 
696     /**
697      * Revoke any active permission grants for the given
698      * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
699      * becomes invalid. Follows the same semantics as
700      * {@link Context#revokeUriPermission(Uri, int)}.
701      */
revokeDocumentPermission(String documentId)702     public final void revokeDocumentPermission(String documentId) {
703         final Context context = getContext();
704         context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
705         context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
706     }
707 
708     /**
709      * Implementation is provided by the parent class. Cannot be overriden.
710      *
711      * @see #openDocument(String, String, CancellationSignal)
712      */
713     @Override
openFile(Uri uri, String mode)714     public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
715         enforceTree(uri);
716         return openDocument(getDocumentId(uri), mode, null);
717     }
718 
719     /**
720      * Implementation is provided by the parent class. Cannot be overriden.
721      *
722      * @see #openDocument(String, String, CancellationSignal)
723      */
724     @Override
openFile(Uri uri, String mode, CancellationSignal signal)725     public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
726             throws FileNotFoundException {
727         enforceTree(uri);
728         return openDocument(getDocumentId(uri), mode, signal);
729     }
730 
731     /**
732      * Implementation is provided by the parent class. Cannot be overriden.
733      *
734      * @see #openDocument(String, String, CancellationSignal)
735      */
736     @Override
737     @SuppressWarnings("resource")
openAssetFile(Uri uri, String mode)738     public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
739             throws FileNotFoundException {
740         enforceTree(uri);
741         final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
742         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
743     }
744 
745     /**
746      * Implementation is provided by the parent class. Cannot be overriden.
747      *
748      * @see #openDocument(String, String, CancellationSignal)
749      */
750     @Override
751     @SuppressWarnings("resource")
openAssetFile(Uri uri, String mode, CancellationSignal signal)752     public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
753             throws FileNotFoundException {
754         enforceTree(uri);
755         final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
756         return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
757     }
758 
759     /**
760      * Implementation is provided by the parent class. Cannot be overriden.
761      *
762      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
763      */
764     @Override
openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)765     public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
766             throws FileNotFoundException {
767         enforceTree(uri);
768         if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
769             final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
770             return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
771         } else {
772             return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
773         }
774     }
775 
776     /**
777      * Implementation is provided by the parent class. Cannot be overriden.
778      *
779      * @see #openDocumentThumbnail(String, Point, CancellationSignal)
780      */
781     @Override
openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)782     public final AssetFileDescriptor openTypedAssetFile(
783             Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
784             throws FileNotFoundException {
785         enforceTree(uri);
786         if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
787             final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
788             return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
789         } else {
790             return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
791         }
792     }
793 }
794