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