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