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