blob: 4bdcdb097df67b014058d3ef9fe48bae574b5f3e [file] [log] [blame]
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001/*
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
Steve McKayd3afdee2015-11-19 17:27:12 -080019import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070020import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +090021import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070022import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
Ben Line7822fb2016-06-24 15:21:08 -070023import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
Garfield Tan3f6b68a2016-11-01 14:13:38 -070024import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH;
Julian Mancinib6505152017-06-27 13:29:09 -070025import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA;
Steve McKayd3afdee2015-11-19 17:27:12 -080026import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +090027import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +090028import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
Steve McKayd3afdee2015-11-19 17:27:12 -080029import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070030import static android.provider.DocumentsContract.buildDocumentUri;
31import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
32import static android.provider.DocumentsContract.buildTreeDocumentUri;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070033import static android.provider.DocumentsContract.getDocumentId;
34import static android.provider.DocumentsContract.getRootId;
35import static android.provider.DocumentsContract.getSearchDocumentsQuery;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070036import static android.provider.DocumentsContract.getTreeDocumentId;
37import static android.provider.DocumentsContract.isTreeUri;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070038
Garfield Tanaba97f32016-10-06 17:34:19 +000039import android.Manifest;
Tor Norbyec615c6f2015-03-02 10:11:44 -080040import android.annotation.CallSuper;
Garfield Tan06940e12016-10-07 16:03:17 -070041import android.annotation.Nullable;
Ben Lindf6d37e2017-03-20 18:51:20 -070042import android.app.AuthenticationRequiredException;
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +090043import android.content.ClipDescription;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070044import android.content.ContentProvider;
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070045import android.content.ContentResolver;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070046import android.content.ContentValues;
47import android.content.Context;
48import android.content.Intent;
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +090049import android.content.IntentSender;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070050import android.content.UriMatcher;
Jeff Sharkeye37ea612013-09-04 14:30:31 -070051import android.content.pm.PackageManager;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070052import android.content.pm.ProviderInfo;
53import android.content.res.AssetFileDescriptor;
54import android.database.Cursor;
55import android.graphics.Point;
56import android.net.Uri;
57import android.os.Bundle;
58import android.os.CancellationSignal;
59import android.os.ParcelFileDescriptor;
Ben Lin8ea82002017-03-08 17:30:16 -080060import android.os.ParcelableException;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070061import android.provider.DocumentsContract.Document;
Garfield Tanaba97f32016-10-06 17:34:19 +000062import android.provider.DocumentsContract.Path;
Garfield Tan06940e12016-10-07 16:03:17 -070063import android.provider.DocumentsContract.Root;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070064import android.util.Log;
65
66import libcore.io.IoUtils;
67
68import java.io.FileNotFoundException;
Garfield Tanb690b4d2017-03-01 16:05:23 -080069import java.util.LinkedList;
Jeff Sharkey21de56a2014-04-05 19:05:24 -070070import java.util.Objects;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070071
72/**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070073 * Base class for a document provider. A document provider offers read and write
74 * access to durable files, such as files stored on a local disk, or files in a
75 * cloud storage service. To create a document provider, extend this class,
76 * implement the abstract methods, and add it to your manifest like this:
77 *
78 * <pre class="prettyprint">&lt;manifest&gt;
79 * ...
80 * &lt;application&gt;
81 * ...
82 * &lt;provider
83 * android:name="com.example.MyCloudProvider"
84 * android:authorities="com.example.mycloudprovider"
85 * android:exported="true"
86 * android:grantUriPermissions="true"
Jeff Sharkey3b945402013-11-13 13:31:09 -080087 * android:permission="android.permission.MANAGE_DOCUMENTS"
88 * android:enabled="@bool/isAtLeastKitKat"&gt;
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070089 * &lt;intent-filter&gt;
90 * &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
91 * &lt;/intent-filter&gt;
92 * &lt;/provider&gt;
93 * ...
94 * &lt;/application&gt;
95 *&lt;/manifest&gt;</pre>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070096 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070097 * When defining your provider, you must protect it with
98 * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
99 * only the system can obtain. Applications cannot use a documents provider
100 * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
101 * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
Jeff Sharkey9352c382013-10-23 12:14:34 -0700102 * navigate and select documents. When a user selects documents through that UI,
103 * the system issues narrow URI permission grants to the requesting application.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700104 * </p>
105 * <h3>Documents</h3>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700106 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700107 * A document can be either an openable stream (with a specific MIME type), or a
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700108 * directory containing additional documents (with the
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700109 * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
110 * of a subtree containing zero or more documents, which can recursively contain
111 * even more documents and directories.
112 * </p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700113 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700114 * Each document can have different capabilities, as described by
115 * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
Jeff Sharkey9352c382013-10-23 12:14:34 -0700116 * as a thumbnail, your provider can set
117 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700118 * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
119 * that thumbnail.
120 * </p>
121 * <p>
122 * Each document under a provider is uniquely referenced by its
123 * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
124 * single document can be included in multiple directories when responding to
125 * {@link #queryChildDocuments(String, String[], String)}. For example, a
126 * provider might surface a single photo in multiple locations: once in a
Jeff Sharkey9352c382013-10-23 12:14:34 -0700127 * directory of geographic locations, and again in a directory of dates.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700128 * </p>
129 * <h3>Roots</h3>
130 * <p>
131 * All documents are surfaced through one or more "roots." Each root represents
132 * the top of a document tree that a user can navigate. For example, a root
133 * could represent an account or a physical storage device. Similar to
134 * documents, each root can have capabilities expressed through
135 * {@link Root#COLUMN_FLAGS}.
136 * </p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700137 *
138 * @see Intent#ACTION_OPEN_DOCUMENT
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700139 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700140 * @see Intent#ACTION_CREATE_DOCUMENT
141 */
142public abstract class DocumentsProvider extends ContentProvider {
143 private static final String TAG = "DocumentsProvider";
144
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700145 private static final int MATCH_ROOTS = 1;
146 private static final int MATCH_ROOT = 2;
147 private static final int MATCH_RECENT = 3;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700148 private static final int MATCH_SEARCH = 4;
149 private static final int MATCH_DOCUMENT = 5;
150 private static final int MATCH_CHILDREN = 6;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700151 private static final int MATCH_DOCUMENT_TREE = 7;
152 private static final int MATCH_CHILDREN_TREE = 8;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700153
154 private String mAuthority;
155
156 private UriMatcher mMatcher;
157
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700158 /**
159 * Implementation is provided by the parent class.
160 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700161 @Override
162 public void attachInfo(Context context, ProviderInfo info) {
Garfield Tan06940e12016-10-07 16:03:17 -0700163 registerAuthority(info.authority);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700164
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
Garfield Tan06940e12016-10-07 16:03:17 -0700180 /** {@hide} */
181 @Override
182 public void attachInfoForTesting(Context context, ProviderInfo info) {
183 registerAuthority(info.authority);
184
185 super.attachInfoForTesting(context, info);
186 }
187
188 private void registerAuthority(String authority) {
189 mAuthority = authority;
190
191 mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
192 mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
193 mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
194 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
195 mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
196 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
197 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
198 mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
199 mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
200 }
201
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700202 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700203 * Test if a document is descendant (child, grandchild, etc) from the given
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700204 * parent. For example, providers must implement this to support
205 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
206 * requests to keep this request fast.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700207 *
208 * @param parentDocumentId parent to verify against.
209 * @param documentId child to verify.
210 * @return if given document is a descendant of the given parent.
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700211 * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700212 */
213 public boolean isChildDocument(String parentDocumentId, String documentId) {
214 return false;
215 }
216
217 /** {@hide} */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700218 private void enforceTree(Uri documentUri) {
219 if (isTreeUri(documentUri)) {
220 final String parent = getTreeDocumentId(documentUri);
221 final String child = getDocumentId(documentUri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700222 if (Objects.equals(parent, child)) {
223 return;
224 }
225 if (!isChildDocument(parent, child)) {
226 throw new SecurityException(
227 "Document " + child + " is not a descendant of " + parent);
228 }
229 }
230 }
231
232 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700233 * Create a new document and return its newly generated
Jeff Sharkey9352c382013-10-23 12:14:34 -0700234 * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700235 * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
236 * not change once returned.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700237 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700238 * @param parentDocumentId the parent directory to create the new document
239 * under.
240 * @param mimeType the concrete MIME type associated with the new document.
241 * If the MIME type is not supported, the provider must throw.
242 * @param displayName the display name of the new document. The provider may
243 * alter this name to meet any internal constraints, such as
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700244 * avoiding conflicting names.
Ben Lindf6d37e2017-03-20 18:51:20 -0700245
246 * @throws AuthenticationRequiredException If authentication is required from the user (such as
247 * login credentials), but it is not guaranteed that the client will handle this
248 * properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700249 */
250 @SuppressWarnings("unused")
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700251 public String createDocument(String parentDocumentId, String mimeType, String displayName)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700252 throws FileNotFoundException {
253 throw new UnsupportedOperationException("Create not supported");
254 }
255
256 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700257 * Rename an existing document.
258 * <p>
259 * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
260 * represent the renamed document, generate and return it. Any outstanding
261 * URI permission grants will be updated to point at the new document. If
262 * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
263 * rename, return {@code null}.
264 *
265 * @param documentId the document to rename.
266 * @param displayName the updated display name of the document. The provider
267 * may alter this name to meet any internal constraints, such as
268 * avoiding conflicting names.
Ben Lindf6d37e2017-03-20 18:51:20 -0700269 * @throws AuthenticationRequiredException If authentication is required from
270 * the user (such as login credentials), but it is not guaranteed
271 * that the client will handle this properly.
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700272 */
273 @SuppressWarnings("unused")
274 public String renameDocument(String documentId, String displayName)
275 throws FileNotFoundException {
276 throw new UnsupportedOperationException("Rename not supported");
277 }
278
279 /**
280 * Delete the requested document.
281 * <p>
282 * Upon returning, any URI permission grants for the given document will be
283 * revoked. If additional documents were deleted as a side effect of this
284 * call (such as documents inside a directory) the implementor is
285 * responsible for revoking those permissions using
286 * {@link #revokeDocumentPermission(String)}.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700287 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700288 * @param documentId the document to delete.
Ben Lindf6d37e2017-03-20 18:51:20 -0700289 * @throws AuthenticationRequiredException If authentication is required from
290 * the user (such as login credentials), but it is not guaranteed
291 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700292 */
293 @SuppressWarnings("unused")
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700294 public void deleteDocument(String documentId) throws FileNotFoundException {
295 throw new UnsupportedOperationException("Delete not supported");
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700296 }
297
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700298 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700299 * Copy the requested document or a document tree.
300 * <p>
301 * Copies a document including all child documents to another location within
302 * the same document provider. Upon completion returns the document id of
303 * the copied document at the target destination. {@code null} must never
304 * be returned.
305 *
306 * @param sourceDocumentId the document to copy.
307 * @param targetParentDocumentId the target document to be copied into as a child.
Ben Lindf6d37e2017-03-20 18:51:20 -0700308 * @throws AuthenticationRequiredException If authentication is required from
309 * the user (such as login credentials), but it is not guaranteed
310 * that the client will handle this properly.
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700311 */
312 @SuppressWarnings("unused")
313 public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
314 throws FileNotFoundException {
315 throw new UnsupportedOperationException("Copy not supported");
316 }
317
318 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900319 * Move the requested document or a document tree.
Tomasz Mikolajewskieeb8b602016-01-28 13:00:12 +0900320 *
321 * <p>Moves a document including all child documents to another location within
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900322 * the same document provider. Upon completion returns the document id of
323 * the copied document at the target destination. {@code null} must never
324 * be returned.
325 *
Tomasz Mikolajewskieeb8b602016-01-28 13:00:12 +0900326 * <p>It's the responsibility of the provider to revoke grants if the document
327 * is no longer accessible using <code>sourceDocumentId</code>.
328 *
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900329 * @param sourceDocumentId the document to move.
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900330 * @param sourceParentDocumentId the parent of the document to move.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900331 * @param targetParentDocumentId the target document to be a new parent of the
332 * source document.
Ben Lindf6d37e2017-03-20 18:51:20 -0700333 * @throws AuthenticationRequiredException If authentication is required from
334 * the user (such as login credentials), but it is not guaranteed
335 * that the client will handle this properly.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900336 */
337 @SuppressWarnings("unused")
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900338 public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
339 String targetParentDocumentId)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900340 throws FileNotFoundException {
341 throw new UnsupportedOperationException("Move not supported");
342 }
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900343
344 /**
345 * Removes the requested document or a document tree.
346 *
347 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
348 * This method is especially useful if the document can be in multiple parents.
349 *
350 * <p>It's the responsibility of the provider to revoke grants if the document is
351 * removed from the last parent, and effectively the document is deleted.
352 *
353 * @param documentId the document to remove.
354 * @param parentDocumentId the parent of the document to move.
Ben Lindf6d37e2017-03-20 18:51:20 -0700355 * @throws AuthenticationRequiredException If authentication is required from
356 * the user (such as login credentials), but it is not guaranteed
357 * that the client will handle this properly.
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900358 */
359 @SuppressWarnings("unused")
Tomasz Mikolajewski30714012016-02-15 17:07:37 +0900360 public void removeDocument(String documentId, String parentDocumentId)
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900361 throws FileNotFoundException {
362 throw new UnsupportedOperationException("Remove not supported");
363 }
364
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900365 /**
Garfield Tan06940e12016-10-07 16:03:17 -0700366 * Finds the canonical path for the requested document. The path must start
367 * from the parent document if parentDocumentId is not null or the root document
368 * if parentDocumentId is null. If there are more than one path to this document,
369 * return the most typical one. Include both the parent document or root document
370 * and the requested document in the returned path.
Garfield Tanaba97f32016-10-06 17:34:19 +0000371 *
Garfield Tan06940e12016-10-07 16:03:17 -0700372 * <p>This API assumes that document ID has enough info to infer the root.
373 * Different roots should use different document ID to refer to the same
Garfield Tanaba97f32016-10-06 17:34:19 +0000374 * document.
Ben Lin8ea82002017-03-08 17:30:16 -0800375 *
Garfield Tanaba97f32016-10-06 17:34:19 +0000376 *
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700377 * @param parentDocumentId the document from which the path starts if not null,
378 * or null to indicate a path from the root is requested.
Garfield Tanb690b4d2017-03-01 16:05:23 -0800379 * @param childDocumentId the document which path is requested.
Garfield Tan06940e12016-10-07 16:03:17 -0700380 * @return the path of the requested document. If parentDocumentId is null
381 * returned root ID must not be null. If parentDocumentId is not null
382 * returned root ID must be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700383 * @throws AuthenticationRequiredException If authentication is required from
384 * the user (such as login credentials), but it is not guaranteed
385 * that the client will handle this properly.
Garfield Tanaba97f32016-10-06 17:34:19 +0000386 */
Garfield Tanb690b4d2017-03-01 16:05:23 -0800387 public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
Garfield Tanaba97f32016-10-06 17:34:19 +0000388 throws FileNotFoundException {
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700389 throw new UnsupportedOperationException("findDocumentPath not supported.");
Garfield Tanaba97f32016-10-06 17:34:19 +0000390 }
391
392 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900393 * Creates an intent sender for a web link, if the document is web linkable.
394 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700395 * {@link AuthenticationRequiredException} can be thrown if user does not have
Ben Lin8ea82002017-03-08 17:30:16 -0800396 * sufficient permission for the linked document. Before any new permissions
397 * are granted for the linked document, a visible UI must be shown, so the
398 * user can explicitly confirm whether the permission grants are expected.
399 * The user must be able to cancel the operation.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900400 * <p>
401 * Options passed as an argument may include a list of recipients, such
402 * as email addresses. The provider should reflect these options if possible,
403 * but it's acceptable to ignore them. In either case, confirmation UI must
404 * be shown before any new permission grants are granted.
405 * <p>
406 * It is all right to generate a web link without granting new permissions,
407 * if opening the link would result in a page for requesting permission
408 * access. If it's impossible then the operation must fail by throwing an exception.
409 *
410 * @param documentId the document to create a web link intent for.
411 * @param options additional information, such as list of recipients. Optional.
Ben Lindf6d37e2017-03-20 18:51:20 -0700412 * @throws AuthenticationRequiredException If authentication is required from
413 * the user (such as login credentials), but it is not guaranteed
414 * that the client will handle this properly.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900415 *
416 * @see DocumentsContract.Document#FLAG_WEB_LINKABLE
417 * @see android.app.PendingIntent#getIntentSender
418 */
419 public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options)
420 throws FileNotFoundException {
421 throw new UnsupportedOperationException("createWebLink is not supported.");
422 }
423
424 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700425 * Return all roots currently provided. To display to users, you must define
426 * at least one root. You should avoid making network requests to keep this
427 * request fast.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700428 * <p>
429 * Each root is defined by the metadata columns described in {@link Root},
430 * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
431 * representing a tree of documents to display under that root.
432 * <p>
433 * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
Jeff Sharkey9352c382013-10-23 12:14:34 -0700434 * android.database.ContentObserver, boolean)} with
435 * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
Ben Lin8ea82002017-03-08 17:30:16 -0800436 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700437 *
438 * @param projection list of {@link Root} columns to put into the cursor. If
439 * {@code null} all supported columns should be included.
440 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700441 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
442
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700443 /**
444 * Return recently modified documents under the requested root. This will
445 * only be called for roots that advertise
446 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
447 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
448 * limited to only return the 64 most recently modified documents.
Jeff Sharkey37ed78e2013-11-05 12:38:21 -0800449 * <p>
450 * Recent documents do not support change notifications.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700451 *
452 * @param projection list of {@link Document} columns to put into the
453 * cursor. If {@code null} all supported columns should be
454 * included.
455 * @see DocumentsContract#EXTRA_LOADING
456 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700457 @SuppressWarnings("unused")
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700458 public Cursor queryRecentDocuments(String rootId, String[] projection)
459 throws FileNotFoundException {
460 throw new UnsupportedOperationException("Recent not supported");
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700461 }
462
463 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700464 * Return metadata for the single requested document. You should avoid
465 * making network requests to keep this request fast.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700466 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700467 * @param documentId the document to return.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700468 * @param projection list of {@link Document} columns to put into the
469 * cursor. If {@code null} all supported columns should be
470 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700471 * @throws AuthenticationRequiredException If authentication is required from
472 * the user (such as login credentials), but it is not guaranteed
473 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700474 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700475 public abstract Cursor queryDocument(String documentId, String[] projection)
476 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700477
478 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700479 * Return the children documents contained in the requested directory. This
480 * must only return immediate descendants, as additional queries will be
481 * issued to recursively explore the tree.
482 * <p>
Steve McKay29c3f682016-12-16 14:52:59 -0800483 * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
484 * should override {@link #queryChildDocuments(String, String[], Bundle)}.
485 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700486 * If your provider is cloud-based, and you have some data cached or pinned
487 * locally, you may return the local data immediately, setting
488 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700489 * you are still fetching additional data. Then, when the network data is
490 * available, you can send a change notification to trigger a requery and
Jeff Sharkey3b945402013-11-13 13:31:09 -0800491 * return the complete contents. To return a Cursor with extras, you need to
492 * extend and override {@link Cursor#getExtras()}.
Jeff Sharkey9352c382013-10-23 12:14:34 -0700493 * <p>
494 * To support change notifications, you must
495 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
496 * Uri, such as
497 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
498 * you can call {@link ContentResolver#notifyChange(Uri,
499 * android.database.ContentObserver, boolean)} with that Uri to send change
500 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700501 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700502 * @param parentDocumentId the directory to return children for.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700503 * @param projection list of {@link Document} columns to put into the
504 * cursor. If {@code null} all supported columns should be
505 * included.
506 * @param sortOrder how to order the rows, formatted as an SQL
507 * {@code ORDER BY} clause (excluding the ORDER BY itself).
508 * Passing {@code null} will use the default sort order, which
509 * may be unordered. This ordering is a hint that can be used to
510 * prioritize how data is fetched from the network, but UI may
511 * always enforce a specific ordering.
Ben Lindf6d37e2017-03-20 18:51:20 -0700512 * @throws AuthenticationRequiredException If authentication is required from
513 * the user (such as login credentials), but it is not guaranteed
514 * that the client will handle this properly.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700515 * @see DocumentsContract#EXTRA_LOADING
516 * @see DocumentsContract#EXTRA_INFO
517 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700518 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700519 public abstract Cursor queryChildDocuments(
520 String parentDocumentId, String[] projection, String sortOrder)
521 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700522
Steve McKay29c3f682016-12-16 14:52:59 -0800523 /**
524 * Override this method to return the children documents contained
525 * in the requested directory. This must return immediate descendants only.
526 *
527 * <p>If your provider is cloud-based, and you have data cached
528 * locally, you may return the local data immediately, setting
529 * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that
530 * you are still fetching additional data. Then, when the network data is
531 * available, you can send a change notification to trigger a requery and
532 * return the complete contents. To return a Cursor with extras, you need to
533 * extend and override {@link Cursor#getExtras()}.
534 *
535 * <p>To support change notifications, you must
536 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
537 * Uri, such as
538 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
539 * you can call {@link ContentResolver#notifyChange(Uri,
540 * android.database.ContentObserver, boolean)} with that Uri to send change
541 * notifications.
542 *
543 * @param parentDocumentId the directory to return children for.
544 * @param projection list of {@link Document} columns to put into the
545 * cursor. If {@code null} all supported columns should be
546 * included.
547 * @param queryArgs Bundle containing sorting information or other
548 * argument useful to the provider. If no sorting
549 * information is available, default sorting
550 * will be used, which may be unordered. See
551 * {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
552 * details.
Ben Lindf6d37e2017-03-20 18:51:20 -0700553 * @throws AuthenticationRequiredException If authentication is required from
554 * the user (such as login credentials), but it is not guaranteed
555 * that the client will handle this properly.
Steve McKay29c3f682016-12-16 14:52:59 -0800556 *
557 * @see DocumentsContract#EXTRA_LOADING
558 * @see DocumentsContract#EXTRA_INFO
559 * @see DocumentsContract#EXTRA_ERROR
560 */
561 public Cursor queryChildDocuments(
562 String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
563 throws FileNotFoundException {
564
565 return queryChildDocuments(
566 parentDocumentId, projection, getSortClause(queryArgs));
567 }
568
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700569 /** {@hide} */
570 @SuppressWarnings("unused")
571 public Cursor queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800572 String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700573 throws FileNotFoundException {
574 throw new UnsupportedOperationException("Manage not supported");
575 }
576
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700577 /**
Mark Dolinerd0646dc2014-08-27 16:04:02 -0700578 * Return documents that match the given query under the requested
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700579 * root. The returned documents should be sorted by relevance in descending
580 * order. How documents are matched against the query string is an
581 * implementation detail left to each provider, but it's suggested that at
582 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
583 * case-insensitive fashion.
584 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700585 * If your provider is cloud-based, and you have some data cached or pinned
586 * locally, you may return the local data immediately, setting
587 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
588 * you are still fetching additional data. Then, when the network data is
589 * available, you can send a change notification to trigger a requery and
590 * return the complete contents.
591 * <p>
592 * To support change notifications, you must
593 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
594 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
595 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
596 * android.database.ContentObserver, boolean)} with that Uri to send change
597 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700598 *
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700599 * @param rootId the root to search under.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700600 * @param query string to match documents against.
601 * @param projection list of {@link Document} columns to put into the
602 * cursor. If {@code null} all supported columns should be
603 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700604 * @throws AuthenticationRequiredException If authentication is required from
605 * the user (such as login credentials), but it is not guaranteed
606 * that the client will handle this properly.
607 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700608 * @see DocumentsContract#EXTRA_LOADING
609 * @see DocumentsContract#EXTRA_INFO
610 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700611 */
612 @SuppressWarnings("unused")
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700613 public Cursor querySearchDocuments(String rootId, String query, String[] projection)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700614 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700615 throw new UnsupportedOperationException("Search not supported");
616 }
617
Garfield Tan87877032017-03-22 12:01:14 -0700618 /**
619 * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
620 *
621 * @param rootId the root to be ejected.
622 * @see Root#FLAG_SUPPORTS_EJECT
623 */
Ben Line7822fb2016-06-24 15:21:08 -0700624 @SuppressWarnings("unused")
Garfield Tan87877032017-03-22 12:01:14 -0700625 public void ejectRoot(String rootId) {
Ben Line7822fb2016-06-24 15:21:08 -0700626 throw new UnsupportedOperationException("Eject not supported");
627 }
628
Julian Mancinib6505152017-06-27 13:29:09 -0700629 /** {@hide} */
630 public @Nullable Bundle getDocumentMetadata(String documentId, @Nullable String[] tags)
631 throws FileNotFoundException {
632 throw new UnsupportedOperationException("Metadata not supported");
633 }
634
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700635 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700636 * Return concrete MIME type of the requested document. Must match the value
637 * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
638 * implementation queries {@link #queryDocument(String, String[])}, so
639 * providers may choose to override this as an optimization.
Ben Lin8ea82002017-03-08 17:30:16 -0800640 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700641 * @throws AuthenticationRequiredException If authentication is required from
642 * the user (such as login credentials), but it is not guaranteed
643 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700644 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700645 public String getDocumentType(String documentId) throws FileNotFoundException {
646 final Cursor cursor = queryDocument(documentId, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700647 try {
648 if (cursor.moveToFirst()) {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700649 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700650 } else {
651 return null;
652 }
653 } finally {
654 IoUtils.closeQuietly(cursor);
655 }
656 }
657
658 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700659 * Open and return the requested document.
660 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700661 * Your provider should return a reliable {@link ParcelFileDescriptor} to
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700662 * detect when the remote caller has finished reading or writing the
Garfield Tanaf03e5a2017-05-15 14:19:11 -0700663 * document.
664 * <p>
665 * Mode "r" should always be supported. Provider should throw
666 * {@link UnsupportedOperationException} if the passing mode is not supported.
667 * You may return a pipe or socket pair if the mode is exclusively "r" or
668 * "w", but complex modes like "rw" imply a normal file on disk that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700669 * supports seeking.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700670 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700671 * If you block while downloading content, you should periodically check
672 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700673 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700674 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700675 * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
676 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800677 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700678 * @throws AuthenticationRequiredException If authentication is required from
679 * the user (such as login credentials), but it is not guaranteed
680 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700681 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
682 * OnCloseListener)
683 * @see ParcelFileDescriptor#createReliablePipe()
684 * @see ParcelFileDescriptor#createReliableSocketPair()
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700685 * @see ParcelFileDescriptor#parseMode(String)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700686 */
687 public abstract ParcelFileDescriptor openDocument(
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700688 String documentId, String mode, CancellationSignal signal) throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700689
690 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700691 * Open and return a thumbnail of the requested document.
692 * <p>
693 * A provider should return a thumbnail closely matching the hinted size,
694 * attempting to serve from a local cache if possible. A provider should
695 * never return images more than double the hinted size.
696 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700697 * If you perform expensive operations to download or generate a thumbnail,
698 * you should periodically check {@link CancellationSignal#isCanceled()} to
699 * abort abandoned thumbnail requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700700 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700701 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700702 * @param sizeHint hint of the optimal thumbnail dimensions.
703 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800704 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700705 * @throws AuthenticationRequiredException If authentication is required from
706 * the user (such as login credentials), but it is not guaranteed
707 * that the client will handle this properly.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700708 * @see Document#FLAG_SUPPORTS_THUMBNAIL
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700709 */
710 @SuppressWarnings("unused")
711 public AssetFileDescriptor openDocumentThumbnail(
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700712 String documentId, Point sizeHint, CancellationSignal signal)
713 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700714 throw new UnsupportedOperationException("Thumbnails not supported");
715 }
716
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700717 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900718 * Open and return the document in a format matching the specified MIME
719 * type filter.
720 * <p>
721 * A provider may perform a conversion if the documents's MIME type is not
722 * matching the specified MIME type filter.
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +0900723 * <p>
724 * Virtual documents must have at least one streamable format.
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900725 *
726 * @param documentId the document to return.
727 * @param mimeTypeFilter the MIME type filter for the requested format. May
728 * be *\/*, which matches any MIME type.
729 * @param opts extra options from the client. Specific to the content
730 * provider.
731 * @param signal used by the caller to signal if the request should be
732 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700733 * @throws AuthenticationRequiredException If authentication is required from
734 * the user (such as login credentials), but it is not guaranteed
735 * that the client will handle this properly.
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +0900736 * @see #getDocumentStreamTypes(String, String)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900737 */
738 @SuppressWarnings("unused")
739 public AssetFileDescriptor openTypedDocument(
740 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
741 throws FileNotFoundException {
Tomasz Mikolajewski75395652016-01-07 07:19:22 +0000742 throw new FileNotFoundException("The requested MIME type is not supported.");
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900743 }
744
Steve McKay29c3f682016-12-16 14:52:59 -0800745 @Override
746 public final Cursor query(Uri uri, String[] projection, String selection,
747 String[] selectionArgs, String sortOrder) {
748 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
749 // transport method. We override that, and don't ever delegate to this method.
750 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
751 }
752
Steve McKay0bb25292017-01-24 11:45:18 -0800753 /**
754 * WARNING: Sub-classes should not override this method. This method is non-final
755 * solely for the purposes of backwards compatibility.
756 *
757 * @see #queryChildDocuments(String, String[], Bundle),
758 * {@link #queryDocument(String, String[])},
759 * {@link #queryRecentDocuments(String, String[])},
760 * {@link #queryRoots(String[])}, and
761 * {@link #querySearchDocuments(String, String, String[])}.
762 */
Steve McKay29c3f682016-12-16 14:52:59 -0800763 @Override
Steve McKay0bb25292017-01-24 11:45:18 -0800764 public Cursor query(Uri uri, String[] projection, String selection,
Steve McKay29c3f682016-12-16 14:52:59 -0800765 String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
766 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
767 // transport method. We override that, and don't ever delegate to this metohd.
768 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
769 }
770
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900771 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700772 * Implementation is provided by the parent class. Cannot be overriden.
773 *
774 * @see #queryRoots(String[])
775 * @see #queryRecentDocuments(String, String[])
776 * @see #queryDocument(String, String[])
777 * @see #queryChildDocuments(String, String[], String)
778 * @see #querySearchDocuments(String, String, String[])
779 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700780 @Override
Steve McKay29c3f682016-12-16 14:52:59 -0800781 public final Cursor query(
782 Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700783 try {
784 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700785 case MATCH_ROOTS:
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700786 return queryRoots(projection);
787 case MATCH_RECENT:
788 return queryRecentDocuments(getRootId(uri), projection);
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700789 case MATCH_SEARCH:
790 return querySearchDocuments(
791 getRootId(uri), getSearchDocumentsQuery(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700792 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700793 case MATCH_DOCUMENT_TREE:
794 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700795 return queryDocument(getDocumentId(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700796 case MATCH_CHILDREN:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700797 case MATCH_CHILDREN_TREE:
798 enforceTree(uri);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700799 if (DocumentsContract.isManageMode(uri)) {
Steve McKay29c3f682016-12-16 14:52:59 -0800800 // TODO: Update "ForManage" variant to support query args.
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700801 return queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800802 getDocumentId(uri),
803 projection,
804 getSortClause(queryArgs));
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700805 } else {
Steve McKay29c3f682016-12-16 14:52:59 -0800806 return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700807 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700808 default:
809 throw new UnsupportedOperationException("Unsupported Uri " + uri);
810 }
811 } catch (FileNotFoundException e) {
812 Log.w(TAG, "Failed during query", e);
813 return null;
814 }
815 }
816
Steve McKay29c3f682016-12-16 14:52:59 -0800817 private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
818 queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
819 String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
820
821 if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
822 sortClause = ContentResolver.createSqlSortClause(queryArgs);
823 }
824
825 return sortClause;
826 }
827
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700828 /**
829 * Implementation is provided by the parent class. Cannot be overriden.
830 *
831 * @see #getDocumentType(String)
832 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700833 @Override
834 public final String getType(Uri uri) {
835 try {
836 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700837 case MATCH_ROOT:
838 return DocumentsContract.Root.MIME_TYPE_ITEM;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700839 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700840 case MATCH_DOCUMENT_TREE:
841 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700842 return getDocumentType(getDocumentId(uri));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700843 default:
844 return null;
845 }
846 } catch (FileNotFoundException e) {
847 Log.w(TAG, "Failed during getType", e);
848 return null;
849 }
850 }
851
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700852 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700853 * Implementation is provided by the parent class. Can be overridden to
854 * provide additional functionality, but subclasses <em>must</em> always
855 * call the superclass. If the superclass returns {@code null}, the subclass
856 * may implement custom behavior.
857 * <p>
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700858 * This is typically used to resolve a subtree URI into a concrete document
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700859 * reference, issuing a narrower single-document URI permission grant along
860 * the way.
861 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700862 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700863 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800864 @CallSuper
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700865 @Override
866 public Uri canonicalize(Uri uri) {
867 final Context context = getContext();
868 switch (mMatcher.match(uri)) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700869 case MATCH_DOCUMENT_TREE:
870 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700871
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700872 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700873
874 // Caller may only have prefix grant, so extend them a grant to
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700875 // the narrow URI.
876 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700877 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
878 return narrowUri;
879 }
880 return null;
881 }
882
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700883 private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
884 // TODO: move this to a direct AMS call
885 int modeFlags = 0;
886 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
887 == PackageManager.PERMISSION_GRANTED) {
888 modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
889 }
890 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
891 == PackageManager.PERMISSION_GRANTED) {
892 modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
893 }
894 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
895 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
896 == PackageManager.PERMISSION_GRANTED) {
897 modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
898 }
899 return modeFlags;
900 }
901
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700902 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700903 * Implementation is provided by the parent class. Throws by default, and
904 * cannot be overriden.
905 *
906 * @see #createDocument(String, String, String)
907 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700908 @Override
909 public final Uri insert(Uri uri, ContentValues values) {
910 throw new UnsupportedOperationException("Insert not supported");
911 }
912
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700913 /**
914 * Implementation is provided by the parent class. Throws by default, and
915 * cannot be overriden.
916 *
917 * @see #deleteDocument(String)
918 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700919 @Override
920 public final int delete(Uri uri, String selection, String[] selectionArgs) {
921 throw new UnsupportedOperationException("Delete not supported");
922 }
923
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700924 /**
925 * Implementation is provided by the parent class. Throws by default, and
926 * cannot be overriden.
927 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700928 @Override
929 public final int update(
930 Uri uri, ContentValues values, String selection, String[] selectionArgs) {
931 throw new UnsupportedOperationException("Update not supported");
932 }
933
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700934 /**
935 * Implementation is provided by the parent class. Can be overridden to
936 * provide additional functionality, but subclasses <em>must</em> always
937 * call the superclass. If the superclass returns {@code null}, the subclass
938 * may implement custom behavior.
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700939 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800940 @CallSuper
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700941 @Override
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700942 public Bundle call(String method, String arg, Bundle extras) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700943 if (!method.startsWith("android:")) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700944 // Ignore non-platform methods
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700945 return super.call(method, arg, extras);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700946 }
947
Steve McKayd3afdee2015-11-19 17:27:12 -0800948 try {
949 return callUnchecked(method, arg, extras);
950 } catch (FileNotFoundException e) {
Ben Lin8ea82002017-03-08 17:30:16 -0800951 throw new ParcelableException(e);
Steve McKayd3afdee2015-11-19 17:27:12 -0800952 }
953 }
954
955 private Bundle callUnchecked(String method, String arg, Bundle extras)
956 throws FileNotFoundException {
957
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700958 final Context context = getContext();
Ben Lind7d14872016-06-28 17:12:52 -0700959 final Bundle out = new Bundle();
960
961 if (METHOD_EJECT_ROOT.equals(method)) {
962 // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
963 // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
Garfield Tan87877032017-03-22 12:01:14 -0700964 // MANAGE_DOCUMENTS or associated URI permission here instead
Ben Lind7d14872016-06-28 17:12:52 -0700965 final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
Garfield Tan87877032017-03-22 12:01:14 -0700966 enforceWritePermissionInner(rootUri, getCallingPackage(), null);
Ben Lind7d14872016-06-28 17:12:52 -0700967
Garfield Tan87877032017-03-22 12:01:14 -0700968 final String rootId = DocumentsContract.getRootId(rootUri);
969 ejectRoot(rootId);
Ben Lind7d14872016-06-28 17:12:52 -0700970
971 return out;
972 }
973
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700974 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
975 final String authority = documentUri.getAuthority();
976 final String documentId = DocumentsContract.getDocumentId(documentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -0700977
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700978 if (!mAuthority.equals(authority)) {
979 throw new SecurityException(
980 "Requested authority " + authority + " doesn't match provider " + mAuthority);
981 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700982
Steve McKayd3afdee2015-11-19 17:27:12 -0800983 // If the URI is a tree URI performs some validation.
984 enforceTree(documentUri);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700985
Steve McKayd3afdee2015-11-19 17:27:12 -0800986 if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
987 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
988
989 final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
990 final String childAuthority = childUri.getAuthority();
991 final String childId = DocumentsContract.getDocumentId(childUri);
992
993 out.putBoolean(
994 DocumentsContract.EXTRA_RESULT,
995 mAuthority.equals(childAuthority)
996 && isChildDocument(documentId, childId));
997
998 } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
999 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1000
1001 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
1002 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1003 final String newDocumentId = createDocument(documentId, mimeType, displayName);
1004
1005 // No need to issue new grants here, since caller either has
1006 // manage permission or a prefix grant. We might generate a
1007 // tree style URI if that's how they called us.
1008 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1009 newDocumentId);
1010 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1011
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001012 } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
1013 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1014
1015 final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
1016 final IntentSender intentSender = createWebLinkIntent(documentId, options);
1017
1018 out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
1019
Steve McKayd3afdee2015-11-19 17:27:12 -08001020 } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
1021 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1022
1023 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1024 final String newDocumentId = renameDocument(documentId, displayName);
1025
1026 if (newDocumentId != null) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001027 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001028 newDocumentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001029
1030 // If caller came in with a narrow grant, issue them a
1031 // narrow grant for the newly renamed document.
1032 if (!isTreeUri(newDocumentUri)) {
1033 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1034 documentUri);
1035 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1036 }
1037
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001038 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -07001039
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001040 // Original document no longer exists, clean up any grants.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001041 revokeDocumentPermission(documentId);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001042 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001043
1044 } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
1045 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1046 deleteDocument(documentId);
1047
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001048 // Document no longer exists, clean up any grants.
Steve McKayd3afdee2015-11-19 17:27:12 -08001049 revokeDocumentPermission(documentId);
1050
1051 } else if (METHOD_COPY_DOCUMENT.equals(method)) {
1052 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1053 final String targetId = DocumentsContract.getDocumentId(targetUri);
1054
1055 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1056 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1057
1058 final String newDocumentId = copyDocument(documentId, targetId);
1059
1060 if (newDocumentId != null) {
1061 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1062 newDocumentId);
1063
1064 if (!isTreeUri(newDocumentUri)) {
1065 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1066 documentUri);
1067 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1068 }
1069
1070 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1071 }
1072
1073 } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001074 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1075 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
Steve McKayd3afdee2015-11-19 17:27:12 -08001076 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1077 final String targetId = DocumentsContract.getDocumentId(targetUri);
1078
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001079 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1080 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
Steve McKayd3afdee2015-11-19 17:27:12 -08001081 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1082
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001083 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001084
1085 if (newDocumentId != null) {
1086 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1087 newDocumentId);
1088
1089 if (!isTreeUri(newDocumentUri)) {
1090 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1091 documentUri);
1092 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1093 }
1094
1095 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1096 }
1097
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001098 } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
1099 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1100 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
1101
1102 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
1103 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1104 removeDocument(documentId, parentSourceId);
1105
1106 // It's responsibility of the provider to revoke any grants, as the document may be
1107 // still attached to another parents.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001108 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
Garfield Tan06940e12016-10-07 16:03:17 -07001109 final boolean isTreeUri = isTreeUri(documentUri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001110
Garfield Tan06940e12016-10-07 16:03:17 -07001111 if (isTreeUri) {
1112 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1113 } else {
1114 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
1115 }
1116
1117 final String parentDocumentId = isTreeUri
1118 ? DocumentsContract.getTreeDocumentId(documentUri)
1119 : null;
1120
Garfield Tanb690b4d2017-03-01 16:05:23 -08001121 Path path = findDocumentPath(parentDocumentId, documentId);
Garfield Tan06940e12016-10-07 16:03:17 -07001122
1123 // Ensure provider doesn't leak information to unprivileged callers.
Garfield Tan5f214802016-10-26 14:52:46 -07001124 if (isTreeUri) {
1125 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
1126 Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
1127 + parentDocumentId + " found: " + path.getPath().get(0));
Garfield Tanb690b4d2017-03-01 16:05:23 -08001128
1129 LinkedList<String> docs = new LinkedList<>(path.getPath());
1130 while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
1131 docs.removeFirst();
1132 }
1133 path = new Path(null, docs);
Garfield Tan5f214802016-10-26 14:52:46 -07001134 }
1135
1136 if (path.getRootId() != null) {
1137 Log.wtf(TAG, "Provider returns root id :"
1138 + path.getRootId() + " unexpectedly. Erase root id.");
1139 path = new Path(null, path.getPath());
1140 }
Garfield Tan06940e12016-10-07 16:03:17 -07001141 }
Garfield Tanaba97f32016-10-06 17:34:19 +00001142
1143 out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
Julian Mancinib6505152017-06-27 13:29:09 -07001144 } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
1145 return getDocumentMetadata(
1146 documentId, extras.getStringArray(DocumentsContract.EXTRA_METADATA_TAGS));
Steve McKayd3afdee2015-11-19 17:27:12 -08001147 } else {
1148 throw new UnsupportedOperationException("Method not supported " + method);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001149 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001150
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001151 return out;
1152 }
1153
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001154 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001155 * Revoke any active permission grants for the given
1156 * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
1157 * becomes invalid. Follows the same semantics as
1158 * {@link Context#revokeUriPermission(Uri, int)}.
1159 */
1160 public final void revokeDocumentPermission(String documentId) {
1161 final Context context = getContext();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001162 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
1163 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001164 }
1165
1166 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001167 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001168 *
1169 * @see #openDocument(String, String, CancellationSignal)
1170 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001171 @Override
1172 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001173 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001174 return openDocument(getDocumentId(uri), mode, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001175 }
1176
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001177 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001178 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001179 *
1180 * @see #openDocument(String, String, CancellationSignal)
1181 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001182 @Override
1183 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
1184 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001185 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001186 return openDocument(getDocumentId(uri), mode, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001187 }
1188
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001189 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001190 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001191 *
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001192 * @see #openDocument(String, String, CancellationSignal)
1193 */
1194 @Override
1195 @SuppressWarnings("resource")
1196 public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
1197 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001198 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001199 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
1200 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1201 }
1202
1203 /**
1204 * Implementation is provided by the parent class. Cannot be overriden.
1205 *
1206 * @see #openDocument(String, String, CancellationSignal)
1207 */
1208 @Override
1209 @SuppressWarnings("resource")
1210 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
1211 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001212 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001213 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
1214 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1215 }
1216
1217 /**
1218 * Implementation is provided by the parent class. Cannot be overriden.
1219 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001220 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001221 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001222 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001223 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001224 @Override
1225 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
1226 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001227 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001228 }
1229
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001230 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001231 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001232 *
1233 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001234 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001235 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001236 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001237 @Override
1238 public final AssetFileDescriptor openTypedAssetFile(
1239 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1240 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001241 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
1242 }
1243
1244 /**
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001245 * Return a list of streamable MIME types matching the filter, which can be passed to
1246 * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
1247 *
1248 * <p>The default implementation returns a MIME type provided by
1249 * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
1250 * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
1251 *
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +09001252 * <p>Virtual documents must have at least one streamable format.
1253 *
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001254 * @see #getStreamTypes(Uri, String)
1255 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
1256 */
1257 public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
1258 Cursor cursor = null;
1259 try {
1260 cursor = queryDocument(documentId, null);
1261 if (cursor.moveToFirst()) {
1262 final String mimeType =
1263 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
1264 final long flags =
1265 cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
1266 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
1267 mimeTypeMatches(mimeTypeFilter, mimeType)) {
1268 return new String[] { mimeType };
1269 }
1270 }
1271 } catch (FileNotFoundException e) {
1272 return null;
1273 } finally {
1274 IoUtils.closeQuietly(cursor);
1275 }
1276
1277 // No streamable MIME types.
1278 return null;
1279 }
1280
1281 /**
1282 * Called by a client to determine the types of data streams that this content provider
1283 * support for the given URI.
1284 *
1285 * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
1286 *
1287 * @see #getDocumentStreamTypes(String, String)
1288 */
1289 @Override
1290 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
1291 enforceTree(uri);
1292 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
1293 }
1294
1295 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001296 * @hide
1297 */
1298 private final AssetFileDescriptor openTypedAssetFileImpl(
1299 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1300 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001301 enforceTree(uri);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001302 final String documentId = getDocumentId(uri);
Jeff Sharkey5b836f22014-08-27 14:46:32 -07001303 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
1304 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001305 return openDocumentThumbnail(documentId, sizeHint, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001306 }
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001307 if ("*/*".equals(mimeTypeFilter)) {
1308 // If they can take anything, the untyped open call is good enough.
1309 return openAssetFile(uri, "r");
1310 }
1311 final String baseType = getType(uri);
1312 if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
1313 // Use old untyped open call if this provider has a type for this
1314 // URI and it matches the request.
1315 return openAssetFile(uri, "r");
1316 }
1317 // For any other yet unhandled case, let the provider subclass handle it.
1318 return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001319 }
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001320
1321 /**
1322 * @hide
1323 */
1324 public static boolean mimeTypeMatches(String filter, String test) {
1325 if (test == null) {
1326 return false;
1327 } else if (filter == null || "*/*".equals(filter)) {
1328 return true;
1329 } else if (filter.equals(test)) {
1330 return true;
1331 } else if (filter.endsWith("/*")) {
1332 return filter.regionMatches(0, test, 0, filter.indexOf('/'));
1333 } else {
1334 return false;
1335 }
1336 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001337}