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