blob: c7d37add5134a2c1f692f889d983c22533569f1e [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 /**
Risan6a4a8f62018-10-30 17:57:56 -0600464 * Return recently modified documents under the requested root. This will
465 * only be called for roots that advertise
466 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
467 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order of
468 * the most recently modified documents.
469 * <p>
470 * If this method is overriden by the concrete DocumentsProvider and
471 * QUERY_ARGS_LIMIT is specified with a nonnegative int under queryArgs, the
472 * result will be limited by that number and QUERY_ARG_LIMIT will be
473 * specified under EXTRA_HONORED_ARGS. Otherwise, a default 64 limit will
474 * be used and no QUERY_ARG* will be specified under EXTRA_HONORED_ARGS.
475 * <p>
476 * Recent documents do not support change notifications.
477 *
478 * @param projection list of {@link Document} columns to put into the
479 * cursor. If {@code null} all supported columns should be
480 * included.
481 * @param queryArgs the extra query arguments.
482 * @param signal used by the caller to signal if the request should be
483 * cancelled. May be null.
484 * @see DocumentsContract#EXTRA_LOADING
485 */
486 @SuppressWarnings("unused")
487 public Cursor queryRecentDocuments(
488 String rootId, String[] projection, @Nullable Bundle queryArgs,
489 @Nullable CancellationSignal signal)
490 throws FileNotFoundException {
491 Cursor c = queryRecentDocuments(rootId, projection);
492 Bundle extras = new Bundle();
493 c.setExtras(extras);
494 extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[0]);
495 return c;
496 }
497
498 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700499 * Return metadata for the single requested document. You should avoid
500 * making network requests to keep this request fast.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700501 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700502 * @param documentId the document to return.
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.
Ben Lindf6d37e2017-03-20 18:51:20 -0700506 * @throws AuthenticationRequiredException If authentication is required from
507 * the user (such as login credentials), but it is not guaranteed
508 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700509 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700510 public abstract Cursor queryDocument(String documentId, String[] projection)
511 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700512
513 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700514 * Return the children documents contained in the requested directory. This
515 * must only return immediate descendants, as additional queries will be
516 * issued to recursively explore the tree.
517 * <p>
Steve McKay29c3f682016-12-16 14:52:59 -0800518 * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
519 * should override {@link #queryChildDocuments(String, String[], Bundle)}.
520 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700521 * If your provider is cloud-based, and you have some data cached or pinned
522 * locally, you may return the local data immediately, setting
523 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700524 * you are still fetching additional data. Then, when the network data is
525 * available, you can send a change notification to trigger a requery and
Jeff Sharkey3b945402013-11-13 13:31:09 -0800526 * return the complete contents. To return a Cursor with extras, you need to
527 * extend and override {@link Cursor#getExtras()}.
Jeff Sharkey9352c382013-10-23 12:14:34 -0700528 * <p>
529 * To support change notifications, you must
530 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
531 * Uri, such as
532 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
533 * you can call {@link ContentResolver#notifyChange(Uri,
534 * android.database.ContentObserver, boolean)} with that Uri to send change
535 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700536 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700537 * @param parentDocumentId the directory to return children for.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700538 * @param projection list of {@link Document} columns to put into the
539 * cursor. If {@code null} all supported columns should be
540 * included.
541 * @param sortOrder how to order the rows, formatted as an SQL
542 * {@code ORDER BY} clause (excluding the ORDER BY itself).
543 * Passing {@code null} will use the default sort order, which
544 * may be unordered. This ordering is a hint that can be used to
545 * prioritize how data is fetched from the network, but UI may
546 * always enforce a specific ordering.
Ben Lindf6d37e2017-03-20 18:51:20 -0700547 * @throws AuthenticationRequiredException If authentication is required from
548 * the user (such as login credentials), but it is not guaranteed
549 * that the client will handle this properly.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700550 * @see DocumentsContract#EXTRA_LOADING
551 * @see DocumentsContract#EXTRA_INFO
552 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700553 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700554 public abstract Cursor queryChildDocuments(
555 String parentDocumentId, String[] projection, String sortOrder)
556 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700557
Steve McKay29c3f682016-12-16 14:52:59 -0800558 /**
559 * Override this method to return the children documents contained
560 * in the requested directory. This must return immediate descendants only.
561 *
562 * <p>If your provider is cloud-based, and you have data cached
563 * locally, you may return the local data immediately, setting
564 * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that
565 * you are still fetching additional data. Then, when the network data is
566 * available, you can send a change notification to trigger a requery and
567 * return the complete contents. To return a Cursor with extras, you need to
568 * extend and override {@link Cursor#getExtras()}.
569 *
570 * <p>To support change notifications, you must
571 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
572 * Uri, such as
573 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
574 * you can call {@link ContentResolver#notifyChange(Uri,
575 * android.database.ContentObserver, boolean)} with that Uri to send change
576 * notifications.
577 *
578 * @param parentDocumentId the directory to return children for.
579 * @param projection list of {@link Document} columns to put into the
580 * cursor. If {@code null} all supported columns should be
581 * included.
582 * @param queryArgs Bundle containing sorting information or other
583 * argument useful to the provider. If no sorting
584 * information is available, default sorting
585 * will be used, which may be unordered. See
586 * {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
587 * details.
Ben Lindf6d37e2017-03-20 18:51:20 -0700588 * @throws AuthenticationRequiredException If authentication is required from
589 * the user (such as login credentials), but it is not guaranteed
590 * that the client will handle this properly.
Steve McKay29c3f682016-12-16 14:52:59 -0800591 *
592 * @see DocumentsContract#EXTRA_LOADING
593 * @see DocumentsContract#EXTRA_INFO
594 * @see DocumentsContract#EXTRA_ERROR
595 */
596 public Cursor queryChildDocuments(
597 String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
598 throws FileNotFoundException {
599
600 return queryChildDocuments(
601 parentDocumentId, projection, getSortClause(queryArgs));
602 }
603
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700604 /** {@hide} */
605 @SuppressWarnings("unused")
606 public Cursor queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800607 String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700608 throws FileNotFoundException {
609 throw new UnsupportedOperationException("Manage not supported");
610 }
611
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700612 /**
Mark Dolinerd0646dc2014-08-27 16:04:02 -0700613 * Return documents that match the given query under the requested
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700614 * root. The returned documents should be sorted by relevance in descending
615 * order. How documents are matched against the query string is an
616 * implementation detail left to each provider, but it's suggested that at
617 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
618 * case-insensitive fashion.
619 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700620 * If your provider is cloud-based, and you have some data cached or pinned
621 * locally, you may return the local data immediately, setting
622 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
623 * you are still fetching additional data. Then, when the network data is
624 * available, you can send a change notification to trigger a requery and
625 * return the complete contents.
626 * <p>
627 * To support change notifications, you must
628 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
629 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
630 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
631 * android.database.ContentObserver, boolean)} with that Uri to send change
632 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700633 *
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700634 * @param rootId the root to search under.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700635 * @param query string to match documents against.
636 * @param projection list of {@link Document} columns to put into the
637 * cursor. If {@code null} all supported columns should be
638 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700639 * @throws AuthenticationRequiredException If authentication is required from
640 * the user (such as login credentials), but it is not guaranteed
641 * that the client will handle this properly.
642 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700643 * @see DocumentsContract#EXTRA_LOADING
644 * @see DocumentsContract#EXTRA_INFO
645 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700646 */
647 @SuppressWarnings("unused")
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700648 public Cursor querySearchDocuments(String rootId, String query, String[] projection)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700649 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700650 throw new UnsupportedOperationException("Search not supported");
651 }
652
Garfield Tan87877032017-03-22 12:01:14 -0700653 /**
654 * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
655 *
656 * @param rootId the root to be ejected.
657 * @see Root#FLAG_SUPPORTS_EJECT
658 */
Ben Line7822fb2016-06-24 15:21:08 -0700659 @SuppressWarnings("unused")
Garfield Tan87877032017-03-22 12:01:14 -0700660 public void ejectRoot(String rootId) {
Ben Line7822fb2016-06-24 15:21:08 -0700661 throw new UnsupportedOperationException("Eject not supported");
662 }
663
Julian Mancinib6505152017-06-27 13:29:09 -0700664 /** {@hide} */
Steve McKay17a9ce32017-07-27 13:37:14 -0700665 public @Nullable Bundle getDocumentMetadata(String documentId)
Julian Mancinib6505152017-06-27 13:29:09 -0700666 throws FileNotFoundException {
667 throw new UnsupportedOperationException("Metadata not supported");
668 }
669
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700670 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700671 * Return concrete MIME type of the requested document. Must match the value
672 * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
673 * implementation queries {@link #queryDocument(String, String[])}, so
674 * providers may choose to override this as an optimization.
Ben Lin8ea82002017-03-08 17:30:16 -0800675 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700676 * @throws AuthenticationRequiredException If authentication is required from
677 * the user (such as login credentials), but it is not guaranteed
678 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700679 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700680 public String getDocumentType(String documentId) throws FileNotFoundException {
681 final Cursor cursor = queryDocument(documentId, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700682 try {
683 if (cursor.moveToFirst()) {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700684 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700685 } else {
686 return null;
687 }
688 } finally {
689 IoUtils.closeQuietly(cursor);
690 }
691 }
692
693 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700694 * Open and return the requested document.
695 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700696 * Your provider should return a reliable {@link ParcelFileDescriptor} to
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700697 * detect when the remote caller has finished reading or writing the
Garfield Tanaf03e5a2017-05-15 14:19:11 -0700698 * document.
699 * <p>
700 * Mode "r" should always be supported. Provider should throw
701 * {@link UnsupportedOperationException} if the passing mode is not supported.
702 * You may return a pipe or socket pair if the mode is exclusively "r" or
703 * "w", but complex modes like "rw" imply a normal file on disk that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700704 * supports seeking.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700705 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700706 * If you block while downloading content, you should periodically check
707 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700708 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700709 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700710 * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
711 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800712 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700713 * @throws AuthenticationRequiredException If authentication is required from
714 * the user (such as login credentials), but it is not guaranteed
715 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700716 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
717 * OnCloseListener)
718 * @see ParcelFileDescriptor#createReliablePipe()
719 * @see ParcelFileDescriptor#createReliableSocketPair()
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700720 * @see ParcelFileDescriptor#parseMode(String)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700721 */
722 public abstract ParcelFileDescriptor openDocument(
Steve McKay36f1d7e2017-07-20 11:41:58 -0700723 String documentId,
724 String mode,
725 @Nullable CancellationSignal signal) throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700726
727 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700728 * Open and return a thumbnail of the requested document.
729 * <p>
730 * A provider should return a thumbnail closely matching the hinted size,
731 * attempting to serve from a local cache if possible. A provider should
732 * never return images more than double the hinted size.
733 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700734 * If you perform expensive operations to download or generate a thumbnail,
735 * you should periodically check {@link CancellationSignal#isCanceled()} to
736 * abort abandoned thumbnail requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700737 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700738 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700739 * @param sizeHint hint of the optimal thumbnail dimensions.
740 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800741 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700742 * @throws AuthenticationRequiredException If authentication is required from
743 * the user (such as login credentials), but it is not guaranteed
744 * that the client will handle this properly.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700745 * @see Document#FLAG_SUPPORTS_THUMBNAIL
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700746 */
747 @SuppressWarnings("unused")
748 public AssetFileDescriptor openDocumentThumbnail(
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700749 String documentId, Point sizeHint, CancellationSignal signal)
750 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700751 throw new UnsupportedOperationException("Thumbnails not supported");
752 }
753
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700754 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900755 * Open and return the document in a format matching the specified MIME
756 * type filter.
757 * <p>
758 * A provider may perform a conversion if the documents's MIME type is not
759 * matching the specified MIME type filter.
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +0900760 * <p>
761 * Virtual documents must have at least one streamable format.
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900762 *
763 * @param documentId the document to return.
764 * @param mimeTypeFilter the MIME type filter for the requested format. May
765 * be *\/*, which matches any MIME type.
766 * @param opts extra options from the client. Specific to the content
767 * provider.
768 * @param signal used by the caller to signal if the request should be
769 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700770 * @throws AuthenticationRequiredException If authentication is required from
771 * the user (such as login credentials), but it is not guaranteed
772 * that the client will handle this properly.
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +0900773 * @see #getDocumentStreamTypes(String, String)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900774 */
775 @SuppressWarnings("unused")
776 public AssetFileDescriptor openTypedDocument(
777 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
778 throws FileNotFoundException {
Tomasz Mikolajewski75395652016-01-07 07:19:22 +0000779 throw new FileNotFoundException("The requested MIME type is not supported.");
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900780 }
781
Steve McKay29c3f682016-12-16 14:52:59 -0800782 @Override
783 public final Cursor query(Uri uri, String[] projection, String selection,
784 String[] selectionArgs, String sortOrder) {
785 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
786 // transport method. We override that, and don't ever delegate to this method.
787 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
788 }
789
Steve McKay0bb25292017-01-24 11:45:18 -0800790 /**
791 * WARNING: Sub-classes should not override this method. This method is non-final
792 * solely for the purposes of backwards compatibility.
793 *
794 * @see #queryChildDocuments(String, String[], Bundle),
795 * {@link #queryDocument(String, String[])},
796 * {@link #queryRecentDocuments(String, String[])},
797 * {@link #queryRoots(String[])}, and
798 * {@link #querySearchDocuments(String, String, String[])}.
799 */
Steve McKay29c3f682016-12-16 14:52:59 -0800800 @Override
Steve McKay0bb25292017-01-24 11:45:18 -0800801 public Cursor query(Uri uri, String[] projection, String selection,
Steve McKay29c3f682016-12-16 14:52:59 -0800802 String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
803 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
804 // transport method. We override that, and don't ever delegate to this metohd.
805 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
806 }
807
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900808 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700809 * Implementation is provided by the parent class. Cannot be overriden.
810 *
811 * @see #queryRoots(String[])
Risan6a4a8f62018-10-30 17:57:56 -0600812 * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700813 * @see #queryDocument(String, String[])
814 * @see #queryChildDocuments(String, String[], String)
815 * @see #querySearchDocuments(String, String, String[])
816 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700817 @Override
Steve McKay29c3f682016-12-16 14:52:59 -0800818 public final Cursor query(
819 Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700820 try {
821 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700822 case MATCH_ROOTS:
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700823 return queryRoots(projection);
824 case MATCH_RECENT:
Risan6a4a8f62018-10-30 17:57:56 -0600825 return queryRecentDocuments(
826 getRootId(uri), projection, queryArgs, cancellationSignal);
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700827 case MATCH_SEARCH:
828 return querySearchDocuments(
829 getRootId(uri), getSearchDocumentsQuery(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700830 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700831 case MATCH_DOCUMENT_TREE:
832 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700833 return queryDocument(getDocumentId(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700834 case MATCH_CHILDREN:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700835 case MATCH_CHILDREN_TREE:
836 enforceTree(uri);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700837 if (DocumentsContract.isManageMode(uri)) {
Steve McKay29c3f682016-12-16 14:52:59 -0800838 // TODO: Update "ForManage" variant to support query args.
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700839 return queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800840 getDocumentId(uri),
841 projection,
842 getSortClause(queryArgs));
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700843 } else {
Steve McKay29c3f682016-12-16 14:52:59 -0800844 return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700845 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700846 default:
847 throw new UnsupportedOperationException("Unsupported Uri " + uri);
848 }
849 } catch (FileNotFoundException e) {
850 Log.w(TAG, "Failed during query", e);
851 return null;
852 }
853 }
854
Steve McKay29c3f682016-12-16 14:52:59 -0800855 private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
856 queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
857 String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
858
859 if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
860 sortClause = ContentResolver.createSqlSortClause(queryArgs);
861 }
862
863 return sortClause;
864 }
865
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700866 /**
867 * Implementation is provided by the parent class. Cannot be overriden.
868 *
869 * @see #getDocumentType(String)
870 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700871 @Override
872 public final String getType(Uri uri) {
873 try {
874 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700875 case MATCH_ROOT:
876 return DocumentsContract.Root.MIME_TYPE_ITEM;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700877 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700878 case MATCH_DOCUMENT_TREE:
879 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700880 return getDocumentType(getDocumentId(uri));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700881 default:
882 return null;
883 }
884 } catch (FileNotFoundException e) {
885 Log.w(TAG, "Failed during getType", e);
886 return null;
887 }
888 }
889
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700890 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700891 * Implementation is provided by the parent class. Can be overridden to
892 * provide additional functionality, but subclasses <em>must</em> always
893 * call the superclass. If the superclass returns {@code null}, the subclass
894 * may implement custom behavior.
895 * <p>
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700896 * This is typically used to resolve a subtree URI into a concrete document
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700897 * reference, issuing a narrower single-document URI permission grant along
898 * the way.
899 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700900 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700901 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800902 @CallSuper
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700903 @Override
904 public Uri canonicalize(Uri uri) {
905 final Context context = getContext();
906 switch (mMatcher.match(uri)) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700907 case MATCH_DOCUMENT_TREE:
908 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700909
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700910 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700911
912 // Caller may only have prefix grant, so extend them a grant to
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700913 // the narrow URI.
914 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700915 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
916 return narrowUri;
917 }
918 return null;
919 }
920
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700921 private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
922 // TODO: move this to a direct AMS call
923 int modeFlags = 0;
924 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
925 == PackageManager.PERMISSION_GRANTED) {
926 modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
927 }
928 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
929 == PackageManager.PERMISSION_GRANTED) {
930 modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
931 }
932 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
933 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
934 == PackageManager.PERMISSION_GRANTED) {
935 modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
936 }
937 return modeFlags;
938 }
939
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700940 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700941 * Implementation is provided by the parent class. Throws by default, and
942 * cannot be overriden.
943 *
944 * @see #createDocument(String, String, String)
945 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700946 @Override
947 public final Uri insert(Uri uri, ContentValues values) {
948 throw new UnsupportedOperationException("Insert not supported");
949 }
950
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700951 /**
952 * Implementation is provided by the parent class. Throws by default, and
953 * cannot be overriden.
954 *
955 * @see #deleteDocument(String)
956 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700957 @Override
958 public final int delete(Uri uri, String selection, String[] selectionArgs) {
959 throw new UnsupportedOperationException("Delete not supported");
960 }
961
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700962 /**
963 * Implementation is provided by the parent class. Throws by default, and
964 * cannot be overriden.
965 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700966 @Override
967 public final int update(
968 Uri uri, ContentValues values, String selection, String[] selectionArgs) {
969 throw new UnsupportedOperationException("Update not supported");
970 }
971
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700972 /**
973 * Implementation is provided by the parent class. Can be overridden to
974 * provide additional functionality, but subclasses <em>must</em> always
975 * call the superclass. If the superclass returns {@code null}, the subclass
976 * may implement custom behavior.
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700977 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800978 @CallSuper
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700979 @Override
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700980 public Bundle call(String method, String arg, Bundle extras) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700981 if (!method.startsWith("android:")) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700982 // Ignore non-platform methods
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700983 return super.call(method, arg, extras);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700984 }
985
Steve McKayd3afdee2015-11-19 17:27:12 -0800986 try {
987 return callUnchecked(method, arg, extras);
988 } catch (FileNotFoundException e) {
Ben Lin8ea82002017-03-08 17:30:16 -0800989 throw new ParcelableException(e);
Steve McKayd3afdee2015-11-19 17:27:12 -0800990 }
991 }
992
993 private Bundle callUnchecked(String method, String arg, Bundle extras)
994 throws FileNotFoundException {
995
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700996 final Context context = getContext();
Ben Lind7d14872016-06-28 17:12:52 -0700997 final Bundle out = new Bundle();
998
999 if (METHOD_EJECT_ROOT.equals(method)) {
1000 // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
1001 // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
Garfield Tan87877032017-03-22 12:01:14 -07001002 // MANAGE_DOCUMENTS or associated URI permission here instead
Ben Lind7d14872016-06-28 17:12:52 -07001003 final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
Garfield Tan87877032017-03-22 12:01:14 -07001004 enforceWritePermissionInner(rootUri, getCallingPackage(), null);
Ben Lind7d14872016-06-28 17:12:52 -07001005
Garfield Tan87877032017-03-22 12:01:14 -07001006 final String rootId = DocumentsContract.getRootId(rootUri);
1007 ejectRoot(rootId);
Ben Lind7d14872016-06-28 17:12:52 -07001008
1009 return out;
1010 }
1011
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001012 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
1013 final String authority = documentUri.getAuthority();
1014 final String documentId = DocumentsContract.getDocumentId(documentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -07001015
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001016 if (!mAuthority.equals(authority)) {
1017 throw new SecurityException(
1018 "Requested authority " + authority + " doesn't match provider " + mAuthority);
1019 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001020
Steve McKayd3afdee2015-11-19 17:27:12 -08001021 // If the URI is a tree URI performs some validation.
1022 enforceTree(documentUri);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001023
Steve McKayd3afdee2015-11-19 17:27:12 -08001024 if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
1025 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1026
1027 final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1028 final String childAuthority = childUri.getAuthority();
1029 final String childId = DocumentsContract.getDocumentId(childUri);
1030
1031 out.putBoolean(
1032 DocumentsContract.EXTRA_RESULT,
1033 mAuthority.equals(childAuthority)
1034 && isChildDocument(documentId, childId));
1035
1036 } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
1037 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1038
1039 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
1040 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1041 final String newDocumentId = createDocument(documentId, mimeType, displayName);
1042
1043 // No need to issue new grants here, since caller either has
1044 // manage permission or a prefix grant. We might generate a
1045 // tree style URI if that's how they called us.
1046 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1047 newDocumentId);
1048 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1049
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001050 } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
1051 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1052
1053 final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
1054 final IntentSender intentSender = createWebLinkIntent(documentId, options);
1055
1056 out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
1057
Steve McKayd3afdee2015-11-19 17:27:12 -08001058 } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
1059 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1060
1061 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1062 final String newDocumentId = renameDocument(documentId, displayName);
1063
1064 if (newDocumentId != null) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001065 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001066 newDocumentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001067
1068 // If caller came in with a narrow grant, issue them a
1069 // narrow grant for the newly renamed document.
1070 if (!isTreeUri(newDocumentUri)) {
1071 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1072 documentUri);
1073 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1074 }
1075
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001076 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -07001077
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001078 // Original document no longer exists, clean up any grants.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001079 revokeDocumentPermission(documentId);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001080 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001081
1082 } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
1083 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1084 deleteDocument(documentId);
1085
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001086 // Document no longer exists, clean up any grants.
Steve McKayd3afdee2015-11-19 17:27:12 -08001087 revokeDocumentPermission(documentId);
1088
1089 } else if (METHOD_COPY_DOCUMENT.equals(method)) {
1090 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1091 final String targetId = DocumentsContract.getDocumentId(targetUri);
1092
1093 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1094 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1095
1096 final String newDocumentId = copyDocument(documentId, targetId);
1097
1098 if (newDocumentId != null) {
1099 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1100 newDocumentId);
1101
1102 if (!isTreeUri(newDocumentUri)) {
1103 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1104 documentUri);
1105 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1106 }
1107
1108 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1109 }
1110
1111 } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001112 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1113 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
Steve McKayd3afdee2015-11-19 17:27:12 -08001114 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1115 final String targetId = DocumentsContract.getDocumentId(targetUri);
1116
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001117 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1118 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
Steve McKayd3afdee2015-11-19 17:27:12 -08001119 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1120
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001121 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001122
1123 if (newDocumentId != null) {
1124 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1125 newDocumentId);
1126
1127 if (!isTreeUri(newDocumentUri)) {
1128 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1129 documentUri);
1130 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1131 }
1132
1133 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1134 }
1135
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001136 } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
1137 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1138 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
1139
1140 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
1141 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1142 removeDocument(documentId, parentSourceId);
1143
1144 // It's responsibility of the provider to revoke any grants, as the document may be
1145 // still attached to another parents.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001146 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
Garfield Tan06940e12016-10-07 16:03:17 -07001147 final boolean isTreeUri = isTreeUri(documentUri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001148
Garfield Tan06940e12016-10-07 16:03:17 -07001149 if (isTreeUri) {
1150 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1151 } else {
1152 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
1153 }
1154
1155 final String parentDocumentId = isTreeUri
1156 ? DocumentsContract.getTreeDocumentId(documentUri)
1157 : null;
1158
Garfield Tanb690b4d2017-03-01 16:05:23 -08001159 Path path = findDocumentPath(parentDocumentId, documentId);
Garfield Tan06940e12016-10-07 16:03:17 -07001160
1161 // Ensure provider doesn't leak information to unprivileged callers.
Garfield Tan5f214802016-10-26 14:52:46 -07001162 if (isTreeUri) {
1163 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
1164 Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
1165 + parentDocumentId + " found: " + path.getPath().get(0));
Garfield Tanb690b4d2017-03-01 16:05:23 -08001166
1167 LinkedList<String> docs = new LinkedList<>(path.getPath());
1168 while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
1169 docs.removeFirst();
1170 }
1171 path = new Path(null, docs);
Garfield Tan5f214802016-10-26 14:52:46 -07001172 }
1173
1174 if (path.getRootId() != null) {
1175 Log.wtf(TAG, "Provider returns root id :"
1176 + path.getRootId() + " unexpectedly. Erase root id.");
1177 path = new Path(null, path.getPath());
1178 }
Garfield Tan06940e12016-10-07 16:03:17 -07001179 }
Garfield Tanaba97f32016-10-06 17:34:19 +00001180
1181 out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
Julian Mancinib6505152017-06-27 13:29:09 -07001182 } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
Steve McKay17a9ce32017-07-27 13:37:14 -07001183 return getDocumentMetadata(documentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001184 } else {
1185 throw new UnsupportedOperationException("Method not supported " + method);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001186 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001187
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001188 return out;
1189 }
1190
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001191 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001192 * Revoke any active permission grants for the given
1193 * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
1194 * becomes invalid. Follows the same semantics as
1195 * {@link Context#revokeUriPermission(Uri, int)}.
1196 */
1197 public final void revokeDocumentPermission(String documentId) {
1198 final Context context = getContext();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001199 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
1200 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001201 }
1202
1203 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001204 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001205 *
1206 * @see #openDocument(String, String, CancellationSignal)
1207 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001208 @Override
1209 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001210 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001211 return openDocument(getDocumentId(uri), mode, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001212 }
1213
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001214 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001215 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001216 *
1217 * @see #openDocument(String, String, CancellationSignal)
1218 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001219 @Override
1220 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
1221 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001222 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001223 return openDocument(getDocumentId(uri), mode, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001224 }
1225
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001226 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001227 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001228 *
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001229 * @see #openDocument(String, String, CancellationSignal)
1230 */
1231 @Override
1232 @SuppressWarnings("resource")
1233 public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
1234 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001235 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001236 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
1237 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1238 }
1239
1240 /**
1241 * Implementation is provided by the parent class. Cannot be overriden.
1242 *
1243 * @see #openDocument(String, String, CancellationSignal)
1244 */
1245 @Override
1246 @SuppressWarnings("resource")
1247 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
1248 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001249 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001250 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
1251 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1252 }
1253
1254 /**
1255 * Implementation is provided by the parent class. Cannot be overriden.
1256 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001257 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001258 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001259 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001260 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001261 @Override
1262 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
1263 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001264 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001265 }
1266
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001267 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001268 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001269 *
1270 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001271 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001272 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001273 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001274 @Override
1275 public final AssetFileDescriptor openTypedAssetFile(
1276 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1277 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001278 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
1279 }
1280
1281 /**
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001282 * Return a list of streamable MIME types matching the filter, which can be passed to
1283 * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
1284 *
1285 * <p>The default implementation returns a MIME type provided by
1286 * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
1287 * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
1288 *
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +09001289 * <p>Virtual documents must have at least one streamable format.
1290 *
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001291 * @see #getStreamTypes(Uri, String)
1292 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
1293 */
1294 public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
1295 Cursor cursor = null;
1296 try {
1297 cursor = queryDocument(documentId, null);
1298 if (cursor.moveToFirst()) {
1299 final String mimeType =
1300 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
1301 final long flags =
1302 cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
1303 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
1304 mimeTypeMatches(mimeTypeFilter, mimeType)) {
1305 return new String[] { mimeType };
1306 }
1307 }
1308 } catch (FileNotFoundException e) {
1309 return null;
1310 } finally {
1311 IoUtils.closeQuietly(cursor);
1312 }
1313
1314 // No streamable MIME types.
1315 return null;
1316 }
1317
1318 /**
1319 * Called by a client to determine the types of data streams that this content provider
1320 * support for the given URI.
1321 *
1322 * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
1323 *
1324 * @see #getDocumentStreamTypes(String, String)
1325 */
1326 @Override
1327 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
1328 enforceTree(uri);
1329 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
1330 }
1331
1332 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001333 * @hide
1334 */
1335 private final AssetFileDescriptor openTypedAssetFileImpl(
1336 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1337 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001338 enforceTree(uri);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001339 final String documentId = getDocumentId(uri);
Jeff Sharkey5b836f22014-08-27 14:46:32 -07001340 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
1341 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001342 return openDocumentThumbnail(documentId, sizeHint, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001343 }
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001344 if ("*/*".equals(mimeTypeFilter)) {
1345 // If they can take anything, the untyped open call is good enough.
1346 return openAssetFile(uri, "r");
1347 }
1348 final String baseType = getType(uri);
1349 if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
1350 // Use old untyped open call if this provider has a type for this
1351 // URI and it matches the request.
1352 return openAssetFile(uri, "r");
1353 }
1354 // For any other yet unhandled case, let the provider subclass handle it.
1355 return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001356 }
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001357
1358 /**
1359 * @hide
1360 */
1361 public static boolean mimeTypeMatches(String filter, String test) {
1362 if (test == null) {
1363 return false;
1364 } else if (filter == null || "*/*".equals(filter)) {
1365 return true;
1366 } else if (filter.equals(test)) {
1367 return true;
1368 } else if (filter.endsWith("/*")) {
1369 return filter.regionMatches(0, test, 0, filter.indexOf('/'));
1370 } else {
1371 return false;
1372 }
1373 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001374}