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