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