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