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