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