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