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