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