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