DocumentsProvider.java revision b9fbb7290b02de1ce621deaa2d28a5e42f2e0937
1aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey/*
2aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * Copyright (C) 2013 The Android Open Source Project
3aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey *
4aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
5aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * you may not use this file except in compliance with the License.
6aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * You may obtain a copy of the License at
7aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey *
8aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
9aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey *
10aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * Unless required by applicable law or agreed to in writing, software
11aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
12aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * See the License for the specific language governing permissions and
14aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * limitations under the License.
15aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */
16aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
17aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeypackage android.provider;
18aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
19aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport static android.provider.DocumentsContract.EXTRA_THUMBNAIL_SIZE;
20aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
21aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
22b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkeyimport static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
23b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkeyimport static android.provider.DocumentsContract.buildDocumentUri;
24b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkeyimport static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
25b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkeyimport static android.provider.DocumentsContract.buildTreeDocumentUri;
26ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport static android.provider.DocumentsContract.getDocumentId;
27ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport static android.provider.DocumentsContract.getRootId;
28ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport static android.provider.DocumentsContract.getSearchDocumentsQuery;
29b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkeyimport static android.provider.DocumentsContract.getTreeDocumentId;
30b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkeyimport static android.provider.DocumentsContract.isTreeUri;
31aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
32aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.ContentProvider;
33e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkeyimport android.content.ContentResolver;
34aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.ContentValues;
35aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.Context;
36aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.Intent;
37aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.UriMatcher;
38e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkeyimport android.content.pm.PackageManager;
39aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.pm.ProviderInfo;
40aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.content.res.AssetFileDescriptor;
41aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.database.Cursor;
42aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.graphics.Point;
43aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.net.Uri;
44aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.Bundle;
45aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.CancellationSignal;
46aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.ParcelFileDescriptor;
47aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.os.ParcelFileDescriptor.OnCloseListener;
48ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkeyimport android.provider.DocumentsContract.Document;
49e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkeyimport android.provider.DocumentsContract.Root;
50aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport android.util.Log;
51aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
52aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport libcore.io.IoUtils;
53aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
54aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeyimport java.io.FileNotFoundException;
5521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkeyimport java.util.Objects;
56aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
57aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey/**
58e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Base class for a document provider. A document provider offers read and write
59e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * access to durable files, such as files stored on a local disk, or files in a
60e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * cloud storage service. To create a document provider, extend this class,
61e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * implement the abstract methods, and add it to your manifest like this:
62e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *
63e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <pre class="prettyprint">&lt;manifest&gt;
64e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *    ...
65e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *    &lt;application&gt;
66e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *        ...
67e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *        &lt;provider
68e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *            android:name="com.example.MyCloudProvider"
69e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *            android:authorities="com.example.mycloudprovider"
70e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *            android:exported="true"
71e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *            android:grantUriPermissions="true"
723b945405cf96eae8b882f87934222a453718a559Jeff Sharkey *            android:permission="android.permission.MANAGE_DOCUMENTS"
733b945405cf96eae8b882f87934222a453718a559Jeff Sharkey *            android:enabled="@bool/isAtLeastKitKat"&gt;
74e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *            &lt;intent-filter&gt;
75e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *                &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
76e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *            &lt;/intent-filter&gt;
77e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *        &lt;/provider&gt;
78e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *        ...
79e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *    &lt;/application&gt;
80e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey *&lt;/manifest&gt;</pre>
81aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * <p>
82e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * When defining your provider, you must protect it with
83e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
84e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * only the system can obtain. Applications cannot use a documents provider
85e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
86e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
879352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey * navigate and select documents. When a user selects documents through that UI,
889352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey * the system issues narrow URI permission grants to the requesting application.
89e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p>
90e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <h3>Documents</h3>
91aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * <p>
92e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * A document can be either an openable stream (with a specific MIME type), or a
93aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * directory containing additional documents (with the
94e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
95e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * of a subtree containing zero or more documents, which can recursively contain
96e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * even more documents and directories.
97e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p>
98e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p>
99e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Each document can have different capabilities, as described by
100e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
1019352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey * as a thumbnail, your provider can set
1029352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
103e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
104e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * that thumbnail.
105e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p>
106e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <p>
107e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * Each document under a provider is uniquely referenced by its
108e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
109e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * single document can be included in multiple directories when responding to
110e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link #queryChildDocuments(String, String[], String)}. For example, a
111e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * provider might surface a single photo in multiple locations: once in a
1129352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey * directory of geographic locations, and again in a directory of dates.
113e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p>
114e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * <h3>Roots</h3>
115aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * <p>
116e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * All documents are surfaced through one or more "roots." Each root represents
117e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * the top of a document tree that a user can navigate. For example, a root
118e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * could represent an account or a physical storage device. Similar to
119e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * documents, each root can have capabilities expressed through
120e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * {@link Root#COLUMN_FLAGS}.
121e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey * </p>
122aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey *
123aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see Intent#ACTION_OPEN_DOCUMENT
124b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey * @see Intent#ACTION_OPEN_DOCUMENT_TREE
125aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey * @see Intent#ACTION_CREATE_DOCUMENT
126aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey */
127aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkeypublic abstract class DocumentsProvider extends ContentProvider {
128aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private static final String TAG = "DocumentsProvider";
129aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
130a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey    private static final int MATCH_ROOTS = 1;
131a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey    private static final int MATCH_ROOT = 2;
132a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey    private static final int MATCH_RECENT = 3;
1333e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey    private static final int MATCH_SEARCH = 4;
1343e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey    private static final int MATCH_DOCUMENT = 5;
1353e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey    private static final int MATCH_CHILDREN = 6;
136b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey    private static final int MATCH_DOCUMENT_TREE = 7;
137b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey    private static final int MATCH_CHILDREN_TREE = 8;
138aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
139aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private String mAuthority;
140aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
141aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    private UriMatcher mMatcher;
142aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
143ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
144ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * Implementation is provided by the parent class.
145ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
146aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
147aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public void attachInfo(Context context, ProviderInfo info) {
148aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        mAuthority = info.authority;
149aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
150aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
151a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey        mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
152a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey        mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
153ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
1543e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey        mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
155ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
156ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
157b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
158b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
159aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
160aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        // Sanity check our setup
161aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (!info.exported) {
162aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new SecurityException("Provider must be exported");
163aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
164aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (!info.grantUriPermissions) {
165aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new SecurityException("Provider must grantUriPermissions");
166aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
167aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
168aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
169aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
170aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
171aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
172aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        super.attachInfo(context, info);
173aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
174aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
175aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
17621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * Test if a document is descendant (child, grandchild, etc) from the given
177b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey     * parent. For example, providers must implement this to support
178b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey     * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
179b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey     * requests to keep this request fast.
18021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     *
18121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * @param parentDocumentId parent to verify against.
18221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * @param documentId child to verify.
18321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * @return if given document is a descendant of the given parent.
184b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey     * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
18521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     */
18621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    public boolean isChildDocument(String parentDocumentId, String documentId) {
18721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        return false;
18821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
18921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
19021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    /** {@hide} */
191b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey    private void enforceTree(Uri documentUri) {
192b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        if (isTreeUri(documentUri)) {
193b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey            final String parent = getTreeDocumentId(documentUri);
194b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey            final String child = getDocumentId(documentUri);
19521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            if (Objects.equals(parent, child)) {
19621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                return;
19721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            }
19821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            if (!isChildDocument(parent, child)) {
19921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                throw new SecurityException(
20021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                        "Document " + child + " is not a descendant of " + parent);
20121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            }
20221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        }
20321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
20421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
20521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    /**
206e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Create a new document and return its newly generated
2079352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
208e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
209e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * not change once returned.
210aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
211e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param parentDocumentId the parent directory to create the new document
212e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            under.
213e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param mimeType the concrete MIME type associated with the new document.
214e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            If the MIME type is not supported, the provider must throw.
215e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param displayName the display name of the new document. The provider may
216e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            alter this name to meet any internal constraints, such as
217b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     *            avoiding conflicting names.
218aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
219aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @SuppressWarnings("unused")
220e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey    public String createDocument(String parentDocumentId, String mimeType, String displayName)
221aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
222aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        throw new UnsupportedOperationException("Create not supported");
223aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
224aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
225aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
226b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * Rename an existing document.
227b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * <p>
228b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
229b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * represent the renamed document, generate and return it. Any outstanding
230b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * URI permission grants will be updated to point at the new document. If
231b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
232b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * rename, return {@code null}.
233b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     *
234b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * @param documentId the document to rename.
235b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * @param displayName the updated display name of the document. The provider
236b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     *            may alter this name to meet any internal constraints, such as
237b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     *            avoiding conflicting names.
238b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     */
239b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    @SuppressWarnings("unused")
240b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    public String renameDocument(String documentId, String displayName)
241b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            throws FileNotFoundException {
242b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        throw new UnsupportedOperationException("Rename not supported");
243b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    }
244b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
245b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    /**
246b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * Delete the requested document.
247b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * <p>
248b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * Upon returning, any URI permission grants for the given document will be
249b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * revoked. If additional documents were deleted as a side effect of this
250b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * call (such as documents inside a directory) the implementor is
251b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * responsible for revoking those permissions using
252b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey     * {@link #revokeDocumentPermission(String)}.
253aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
254ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @param documentId the document to delete.
255aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
256aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @SuppressWarnings("unused")
257ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public void deleteDocument(String documentId) throws FileNotFoundException {
258ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        throw new UnsupportedOperationException("Delete not supported");
259aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
260aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
261e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey    /**
2629352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * Return all roots currently provided. To display to users, you must define
2639352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * at least one root. You should avoid making network requests to keep this
2649352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * request fast.
265e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
266e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Each root is defined by the metadata columns described in {@link Root},
267e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
268e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * representing a tree of documents to display under that root.
269e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
270e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
2719352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * android.database.ContentObserver, boolean)} with
2729352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
273e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *
274e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param projection list of {@link Root} columns to put into the cursor. If
275e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            {@code null} all supported columns should be included.
276e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     */
277ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
278ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey
279e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey    /**
280e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Return recently modified documents under the requested root. This will
281e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * only be called for roots that advertise
282e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
283e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
284e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * limited to only return the 64 most recently modified documents.
28537ed78e504ef3666dd5fce15ff4994f151c44fcdJeff Sharkey     * <p>
28637ed78e504ef3666dd5fce15ff4994f151c44fcdJeff Sharkey     * Recent documents do not support change notifications.
287e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *
288e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param projection list of {@link Document} columns to put into the
289e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            cursor. If {@code null} all supported columns should be
290e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            included.
291e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_LOADING
292e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     */
293aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @SuppressWarnings("unused")
294ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public Cursor queryRecentDocuments(String rootId, String[] projection)
295ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException {
296ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        throw new UnsupportedOperationException("Recent not supported");
297aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
298aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
299aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
3009352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * Return metadata for the single requested document. You should avoid
3019352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * making network requests to keep this request fast.
302aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
303ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @param documentId the document to return.
304e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param projection list of {@link Document} columns to put into the
305e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            cursor. If {@code null} all supported columns should be
306e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            included.
307aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
308ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public abstract Cursor queryDocument(String documentId, String[] projection)
309ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException;
310aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
311aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
312e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Return the children documents contained in the requested directory. This
313e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * must only return immediate descendants, as additional queries will be
314e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * issued to recursively explore the tree.
315e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
316e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * If your provider is cloud-based, and you have some data cached or pinned
317e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * locally, you may return the local data immediately, setting
318e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
3199352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * you are still fetching additional data. Then, when the network data is
3209352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * available, you can send a change notification to trigger a requery and
3213b945405cf96eae8b882f87934222a453718a559Jeff Sharkey     * return the complete contents. To return a Cursor with extras, you need to
3223b945405cf96eae8b882f87934222a453718a559Jeff Sharkey     * extend and override {@link Cursor#getExtras()}.
3239352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * <p>
3249352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * To support change notifications, you must
3259352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
3269352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * Uri, such as
3279352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
3289352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * you can call {@link ContentResolver#notifyChange(Uri,
3299352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * android.database.ContentObserver, boolean)} with that Uri to send change
3309352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * notifications.
331aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
332ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @param parentDocumentId the directory to return children for.
333e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param projection list of {@link Document} columns to put into the
334e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            cursor. If {@code null} all supported columns should be
335e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            included.
336e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param sortOrder how to order the rows, formatted as an SQL
337e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            {@code ORDER BY} clause (excluding the ORDER BY itself).
338e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            Passing {@code null} will use the default sort order, which
339e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            may be unordered. This ordering is a hint that can be used to
340e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            prioritize how data is fetched from the network, but UI may
341e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            always enforce a specific ordering.
342e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_LOADING
343e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_INFO
344e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_ERROR
345aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
346ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public abstract Cursor queryChildDocuments(
347ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            String parentDocumentId, String[] projection, String sortOrder)
348ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException;
349aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
3504ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey    /** {@hide} */
3514ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey    @SuppressWarnings("unused")
3524ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey    public Cursor queryChildDocumentsForManage(
3534ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey            String parentDocumentId, String[] projection, String sortOrder)
3544ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey            throws FileNotFoundException {
3554ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey        throw new UnsupportedOperationException("Manage not supported");
3564ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey    }
3574ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey
358aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
359e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Return documents that that match the given query under the requested
360e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * root. The returned documents should be sorted by relevance in descending
361e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * order. How documents are matched against the query string is an
362e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * implementation detail left to each provider, but it's suggested that at
363e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
364e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * case-insensitive fashion.
365e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
366e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Only documents may be returned; directories are not supported in search
367e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * results.
3689352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * <p>
3699352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * If your provider is cloud-based, and you have some data cached or pinned
3709352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * locally, you may return the local data immediately, setting
3719352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
3729352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * you are still fetching additional data. Then, when the network data is
3739352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * available, you can send a change notification to trigger a requery and
3749352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * return the complete contents.
3759352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * <p>
3769352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * To support change notifications, you must
3779352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
3789352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
3799352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
3809352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * android.database.ContentObserver, boolean)} with that Uri to send change
3819352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * notifications.
382aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
3833e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey     * @param rootId the root to search under.
384e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param query string to match documents against.
385e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param projection list of {@link Document} columns to put into the
386e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            cursor. If {@code null} all supported columns should be
387e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     *            included.
388e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_LOADING
389e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_INFO
390e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see DocumentsContract#EXTRA_ERROR
391aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
392aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @SuppressWarnings("unused")
3933e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey    public Cursor querySearchDocuments(String rootId, String query, String[] projection)
394ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            throws FileNotFoundException {
395aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        throw new UnsupportedOperationException("Search not supported");
396aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
397aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
398aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
399e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Return concrete MIME type of the requested document. Must match the value
400e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
401e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * implementation queries {@link #queryDocument(String, String[])}, so
402e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * providers may choose to override this as an optimization.
403aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
404ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public String getDocumentType(String documentId) throws FileNotFoundException {
405ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        final Cursor cursor = queryDocument(documentId, null);
406aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        try {
407aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            if (cursor.moveToFirst()) {
408ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
409aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            } else {
410aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                return null;
411aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            }
412aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } finally {
413aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            IoUtils.closeQuietly(cursor);
414aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
415aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
416aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
417aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
418e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Open and return the requested document.
419e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
4209352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * Your provider should return a reliable {@link ParcelFileDescriptor} to
421e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * detect when the remote caller has finished reading or writing the
4229352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * document. You may return a pipe or socket pair if the mode is exclusively
4239352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * "r" or "w", but complex modes like "rw" imply a normal file on disk that
4249352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * supports seeking.
425e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
4269352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * If you block while downloading content, you should periodically check
4279352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
428aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
429e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param documentId the document to return.
430aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
431aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @param signal used by the caller to signal if the request should be
4323b945405cf96eae8b882f87934222a453718a559Jeff Sharkey     *            cancelled. May be null.
433aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
434aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *      OnCloseListener)
435aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @see ParcelFileDescriptor#createReliablePipe()
436aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @see ParcelFileDescriptor#createReliableSocketPair()
437e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @see ParcelFileDescriptor#parseMode(String)
438aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
439aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public abstract ParcelFileDescriptor openDocument(
440e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey            String documentId, String mode, CancellationSignal signal) throws FileNotFoundException;
441aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
442aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    /**
443e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Open and return a thumbnail of the requested document.
444e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
445e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * A provider should return a thumbnail closely matching the hinted size,
446e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * attempting to serve from a local cache if possible. A provider should
447e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * never return images more than double the hinted size.
448e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * <p>
4499352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * If you perform expensive operations to download or generate a thumbnail,
4509352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * you should periodically check {@link CancellationSignal#isCanceled()} to
4519352c383e95c3e1facd8a7a2d49ff488fb7bbcafJeff Sharkey     * abort abandoned thumbnail requests.
452aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     *
453e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * @param documentId the document to return.
454aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @param sizeHint hint of the optimal thumbnail dimensions.
455aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     * @param signal used by the caller to signal if the request should be
4563b945405cf96eae8b882f87934222a453718a559Jeff Sharkey     *            cancelled. May be null.
457ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see Document#FLAG_SUPPORTS_THUMBNAIL
458aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey     */
459aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @SuppressWarnings("unused")
460aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public AssetFileDescriptor openDocumentThumbnail(
461e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey            String documentId, Point sizeHint, CancellationSignal signal)
462e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey            throws FileNotFoundException {
463aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        throw new UnsupportedOperationException("Thumbnails not supported");
464aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
465aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
466ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
467ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
468ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
469ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #queryRoots(String[])
470ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #queryRecentDocuments(String, String[])
471ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #queryDocument(String, String[])
472ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #queryChildDocuments(String, String[], String)
473ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #querySearchDocuments(String, String, String[])
474ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
475aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
476ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    public final Cursor query(Uri uri, String[] projection, String selection,
477ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            String[] selectionArgs, String sortOrder) {
478aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        try {
479aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            switch (mMatcher.match(uri)) {
480a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey                case MATCH_ROOTS:
481ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                    return queryRoots(projection);
482ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                case MATCH_RECENT:
483ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                    return queryRecentDocuments(getRootId(uri), projection);
4843e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey                case MATCH_SEARCH:
4853e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey                    return querySearchDocuments(
4863e1189b3590aefb65a2af720ae2ba959bbd4188dJeff Sharkey                            getRootId(uri), getSearchDocumentsQuery(uri), projection);
487aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                case MATCH_DOCUMENT:
488b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                case MATCH_DOCUMENT_TREE:
489b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                    enforceTree(uri);
490ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                    return queryDocument(getDocumentId(uri), projection);
491aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                case MATCH_CHILDREN:
492b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                case MATCH_CHILDREN_TREE:
493b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                    enforceTree(uri);
4944ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                    if (DocumentsContract.isManageMode(uri)) {
4954ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                        return queryChildDocumentsForManage(
4964ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                                getDocumentId(uri), projection, sortOrder);
4974ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                    } else {
4984ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                        return queryChildDocuments(getDocumentId(uri), projection, sortOrder);
4994ec973925fc2cd18f9ec0d0ca5af588564fded27Jeff Sharkey                    }
500aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                default:
501aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                    throw new UnsupportedOperationException("Unsupported Uri " + uri);
502aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            }
503aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } catch (FileNotFoundException e) {
504aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            Log.w(TAG, "Failed during query", e);
505aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            return null;
506aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
507aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
508aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
509ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
510ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
511ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
512ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #getDocumentType(String)
513ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
514aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
515aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final String getType(Uri uri) {
516aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        try {
517aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            switch (mMatcher.match(uri)) {
518a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey                case MATCH_ROOT:
519a61dc8e03e6e863005b3a4629ca8f3801d33d3c4Jeff Sharkey                    return DocumentsContract.Root.MIME_TYPE_ITEM;
520aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                case MATCH_DOCUMENT:
521b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                case MATCH_DOCUMENT_TREE:
522b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                    enforceTree(uri);
523ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                    return getDocumentType(getDocumentId(uri));
524aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                default:
525aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                    return null;
526aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            }
527aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } catch (FileNotFoundException e) {
528aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            Log.w(TAG, "Failed during getType", e);
529aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            return null;
530aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
531aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
532aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
533ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
53421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * Implementation is provided by the parent class. Can be overridden to
53521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * provide additional functionality, but subclasses <em>must</em> always
53621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * call the superclass. If the superclass returns {@code null}, the subclass
53721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * may implement custom behavior.
53821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * <p>
539b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey     * This is typically used to resolve a subtree URI into a concrete document
54021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * reference, issuing a narrower single-document URI permission grant along
54121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * the way.
54221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     *
543b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey     * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
54421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     */
54521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    @Override
54621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    public Uri canonicalize(Uri uri) {
54721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final Context context = getContext();
54821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        switch (mMatcher.match(uri)) {
549b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey            case MATCH_DOCUMENT_TREE:
550b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                enforceTree(uri);
55121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
552b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
55321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
55421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                // Caller may only have prefix grant, so extend them a grant to
555b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                // the narrow URI.
556b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
55721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
55821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                return narrowUri;
55921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        }
56021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        return null;
56121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
56221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
563b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
564b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        // TODO: move this to a direct AMS call
565b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        int modeFlags = 0;
566b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
567b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                == PackageManager.PERMISSION_GRANTED) {
568b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
569b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
570b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
571b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                == PackageManager.PERMISSION_GRANTED) {
572b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
573b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
574b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
575b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
576b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                == PackageManager.PERMISSION_GRANTED) {
577b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
578b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        }
579b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        return modeFlags;
580b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey    }
581b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
58221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    /**
583ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * Implementation is provided by the parent class. Throws by default, and
584ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * cannot be overriden.
585ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
586ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #createDocument(String, String, String)
587ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
588aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
589aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final Uri insert(Uri uri, ContentValues values) {
590aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        throw new UnsupportedOperationException("Insert not supported");
591aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
592aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
593ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
594ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * Implementation is provided by the parent class. Throws by default, and
595ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * cannot be overriden.
596ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
597ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #deleteDocument(String)
598ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
599aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
600aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final int delete(Uri uri, String selection, String[] selectionArgs) {
601aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        throw new UnsupportedOperationException("Delete not supported");
602aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
603aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
604ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
605ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * Implementation is provided by the parent class. Throws by default, and
606ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * cannot be overriden.
607ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
608aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
609aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final int update(
610aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            Uri uri, ContentValues values, String selection, String[] selectionArgs) {
611aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        throw new UnsupportedOperationException("Update not supported");
612aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
613aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
614911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey    /**
615911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey     * Implementation is provided by the parent class. Can be overridden to
616911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey     * provide additional functionality, but subclasses <em>must</em> always
617911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey     * call the superclass. If the superclass returns {@code null}, the subclass
618911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey     * may implement custom behavior.
619911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey     */
620aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
621911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey    public Bundle call(String method, String arg, Bundle extras) {
622aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (!method.startsWith("android:")) {
62321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            // Ignore non-platform methods
624911d7f411f36f2279aae44c89ff1d33a29140046Jeff Sharkey            return super.call(method, arg, extras);
625aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
626aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
627b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey        final Context context = getContext();
62821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
62921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final String authority = documentUri.getAuthority();
63021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final String documentId = DocumentsContract.getDocumentId(documentUri);
631e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey
63221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        if (!mAuthority.equals(authority)) {
63321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            throw new SecurityException(
63421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                    "Requested authority " + authority + " doesn't match provider " + mAuthority);
63521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        }
636b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(documentUri);
637aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
638aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        final Bundle out = new Bundle();
639aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        try {
640ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            if (METHOD_CREATE_DOCUMENT.equals(method)) {
64121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                enforceWritePermissionInner(documentUri);
64221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
643ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
644ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
645ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey                final String newDocumentId = createDocument(documentId, mimeType, displayName);
64621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
64721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                // No need to issue new grants here, since caller either has
64821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                // manage permission or a prefix grant. We might generate a
649b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                // tree style URI if that's how they called us.
650b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
65121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                        newDocumentId);
65221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
653e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey
654b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey            } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
655b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                enforceWritePermissionInner(documentUri);
656b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
657b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
658b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                final String newDocumentId = renameDocument(documentId, displayName);
659b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
660b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                if (newDocumentId != null) {
661b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                    final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
662b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                            newDocumentId);
663b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
664b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                    // If caller came in with a narrow grant, issue them a
665b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                    // narrow grant for the newly renamed document.
666b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey                    if (!isTreeUri(newDocumentUri)) {
667b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                        final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
668b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                                documentUri);
669b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                        context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
670b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                    }
671b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
672b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                    out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
673b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
674b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                    // Original document no longer exists, clean up any grants
675b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                    revokeDocumentPermission(documentId);
676b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey                }
677b7e1255d5c8d9e4fa8dd389afb9f5aab35434df3Jeff Sharkey
678aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
67921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                enforceWritePermissionInner(documentUri);
680e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey                deleteDocument(documentId);
681e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey
682e37ea6123d1aa3cd3e8804988886b1f6046d79d6Jeff Sharkey                // Document no longer exists, clean up any grants
68321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey                revokeDocumentPermission(documentId);
684aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
685aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            } else {
686aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey                throw new UnsupportedOperationException("Method not supported " + method);
687aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            }
688aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } catch (FileNotFoundException e) {
689aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throw new IllegalStateException("Failed call " + method, e);
690aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
691aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        return out;
692aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
693aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
694ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
69521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * Revoke any active permission grants for the given
69621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
69721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * becomes invalid. Follows the same semantics as
69821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * {@link Context#revokeUriPermission(Uri, int)}.
69921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     */
70021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    public final void revokeDocumentPermission(String documentId) {
70121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final Context context = getContext();
702b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
703b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
70421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
70521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
70621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    /**
707e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
708ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
709ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #openDocument(String, String, CancellationSignal)
710ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
711aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
712aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
713b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(uri);
714ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        return openDocument(getDocumentId(uri), mode, null);
715aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
716aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
717ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
718e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
719ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
720ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #openDocument(String, String, CancellationSignal)
721ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
722aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
723aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
724aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
725b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(uri);
726ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey        return openDocument(getDocumentId(uri), mode, signal);
727aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
728aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
729ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
730e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
731ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
73221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * @see #openDocument(String, String, CancellationSignal)
73321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     */
73421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    @Override
73521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    @SuppressWarnings("resource")
73621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
73721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            throws FileNotFoundException {
738b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(uri);
73921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
74021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
74121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
74221de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
74321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    /**
74421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
74521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     *
74621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * @see #openDocument(String, String, CancellationSignal)
74721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     */
74821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    @Override
74921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    @SuppressWarnings("resource")
75021de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
75121de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey            throws FileNotFoundException {
752b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(uri);
75321de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
75421de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey        return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
75521de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    }
75621de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey
75721de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey    /**
75821de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
75921de56a94668e0fda1b8bb4ee4f99a09b40d28fdJeff Sharkey     *
760ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
761ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
762aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
763aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
764aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
765b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(uri);
766aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
767aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
768ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            return openDocumentThumbnail(getDocumentId(uri), sizeHint, null);
769aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } else {
770aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            return super.openTypedAssetFile(uri, mimeTypeFilter, opts);
771aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
772aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
773aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey
774ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey    /**
775e8c00d8ed477e199b7f8d1b1e2f37e9cf8593372Jeff Sharkey     * Implementation is provided by the parent class. Cannot be overriden.
776ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     *
777ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     * @see #openDocumentThumbnail(String, Point, CancellationSignal)
778ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey     */
779aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    @Override
780aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    public final AssetFileDescriptor openTypedAssetFile(
781aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
782aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            throws FileNotFoundException {
783b9fbb7290b02de1ce621deaa2d28a5e42f2e0937Jeff Sharkey        enforceTree(uri);
784aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        if (opts != null && opts.containsKey(EXTRA_THUMBNAIL_SIZE)) {
785aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            final Point sizeHint = opts.getParcelable(EXTRA_THUMBNAIL_SIZE);
786ae9b51bfa313c51a31af30875a71255d7b6d2e61Jeff Sharkey            return openDocumentThumbnail(getDocumentId(uri), sizeHint, signal);
787aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        } else {
788aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey            return super.openTypedAssetFile(uri, mimeTypeFilter, opts, signal);
789aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey        }
790aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey    }
791aeb16e2435f9975b9fa1fc4b747796647a21292eJeff Sharkey}
792