blob: 2143a0deb7236f8c66ee96c55e905aa08d7fdbb1 [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;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070035import static android.provider.DocumentsContract.getTreeDocumentId;
36import static android.provider.DocumentsContract.isTreeUri;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070037
Garfield Tanaba97f32016-10-06 17:34:19 +000038import android.Manifest;
Tor Norbyec615c6f2015-03-02 10:11:44 -080039import android.annotation.CallSuper;
Ivan Chiang857a2222019-01-09 15:24:21 +080040import android.annotation.NonNull;
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;
Ivan Chianga972d042018-10-15 15:23:02 +080050import android.content.MimeTypeFilter;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070051import android.content.UriMatcher;
Jeff Sharkeye37ea612013-09-04 14:30:31 -070052import android.content.pm.PackageManager;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070053import android.content.pm.ProviderInfo;
54import android.content.res.AssetFileDescriptor;
55import android.database.Cursor;
56import android.graphics.Point;
57import android.net.Uri;
58import android.os.Bundle;
59import android.os.CancellationSignal;
60import android.os.ParcelFileDescriptor;
Ben Lin8ea82002017-03-08 17:30:16 -080061import android.os.ParcelableException;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070062import android.provider.DocumentsContract.Document;
Garfield Tanaba97f32016-10-06 17:34:19 +000063import android.provider.DocumentsContract.Path;
Garfield Tan06940e12016-10-07 16:03:17 -070064import android.provider.DocumentsContract.Root;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070065import android.util.Log;
66
Ivan Chiang857a2222019-01-09 15:24:21 +080067import com.android.internal.util.Preconditions;
68
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070069import libcore.io.IoUtils;
70
71import java.io.FileNotFoundException;
Garfield Tanb690b4d2017-03-01 16:05:23 -080072import java.util.LinkedList;
Jeff Sharkey21de56a2014-04-05 19:05:24 -070073import java.util.Objects;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070074
75/**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070076 * Base class for a document provider. A document provider offers read and write
77 * access to durable files, such as files stored on a local disk, or files in a
78 * cloud storage service. To create a document provider, extend this class,
79 * implement the abstract methods, and add it to your manifest like this:
80 *
81 * <pre class="prettyprint">&lt;manifest&gt;
82 * ...
83 * &lt;application&gt;
84 * ...
85 * &lt;provider
86 * android:name="com.example.MyCloudProvider"
87 * android:authorities="com.example.mycloudprovider"
88 * android:exported="true"
89 * android:grantUriPermissions="true"
Jeff Sharkey3b945402013-11-13 13:31:09 -080090 * android:permission="android.permission.MANAGE_DOCUMENTS"
91 * android:enabled="@bool/isAtLeastKitKat"&gt;
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070092 * &lt;intent-filter&gt;
93 * &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
94 * &lt;/intent-filter&gt;
95 * &lt;/provider&gt;
96 * ...
97 * &lt;/application&gt;
98 *&lt;/manifest&gt;</pre>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070099 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700100 * When defining your provider, you must protect it with
101 * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
102 * only the system can obtain. Applications cannot use a documents provider
103 * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
104 * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
Jeff Sharkey9352c382013-10-23 12:14:34 -0700105 * navigate and select documents. When a user selects documents through that UI,
106 * the system issues narrow URI permission grants to the requesting application.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700107 * </p>
108 * <h3>Documents</h3>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700109 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700110 * A document can be either an openable stream (with a specific MIME type), or a
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700111 * directory containing additional documents (with the
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700112 * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
113 * of a subtree containing zero or more documents, which can recursively contain
114 * even more documents and directories.
115 * </p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700116 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700117 * Each document can have different capabilities, as described by
118 * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
Jeff Sharkey9352c382013-10-23 12:14:34 -0700119 * as a thumbnail, your provider can set
120 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700121 * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
122 * that thumbnail.
123 * </p>
124 * <p>
125 * Each document under a provider is uniquely referenced by its
126 * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
127 * single document can be included in multiple directories when responding to
128 * {@link #queryChildDocuments(String, String[], String)}. For example, a
129 * provider might surface a single photo in multiple locations: once in a
Jeff Sharkey9352c382013-10-23 12:14:34 -0700130 * directory of geographic locations, and again in a directory of dates.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700131 * </p>
132 * <h3>Roots</h3>
133 * <p>
134 * All documents are surfaced through one or more "roots." Each root represents
135 * the top of a document tree that a user can navigate. For example, a root
136 * could represent an account or a physical storage device. Similar to
137 * documents, each root can have capabilities expressed through
138 * {@link Root#COLUMN_FLAGS}.
139 * </p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700140 *
141 * @see Intent#ACTION_OPEN_DOCUMENT
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700142 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700143 * @see Intent#ACTION_CREATE_DOCUMENT
144 */
145public abstract class DocumentsProvider extends ContentProvider {
146 private static final String TAG = "DocumentsProvider";
147
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700148 private static final int MATCH_ROOTS = 1;
149 private static final int MATCH_ROOT = 2;
150 private static final int MATCH_RECENT = 3;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700151 private static final int MATCH_SEARCH = 4;
152 private static final int MATCH_DOCUMENT = 5;
153 private static final int MATCH_CHILDREN = 6;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700154 private static final int MATCH_DOCUMENT_TREE = 7;
155 private static final int MATCH_CHILDREN_TREE = 8;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700156
157 private String mAuthority;
158
159 private UriMatcher mMatcher;
160
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700161 /**
162 * Implementation is provided by the parent class.
163 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700164 @Override
165 public void attachInfo(Context context, ProviderInfo info) {
Garfield Tan06940e12016-10-07 16:03:17 -0700166 registerAuthority(info.authority);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700167
168 // Sanity check our setup
169 if (!info.exported) {
170 throw new SecurityException("Provider must be exported");
171 }
172 if (!info.grantUriPermissions) {
173 throw new SecurityException("Provider must grantUriPermissions");
174 }
175 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
176 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
177 throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
178 }
179
180 super.attachInfo(context, info);
181 }
182
Garfield Tan06940e12016-10-07 16:03:17 -0700183 /** {@hide} */
184 @Override
185 public void attachInfoForTesting(Context context, ProviderInfo info) {
186 registerAuthority(info.authority);
187
188 super.attachInfoForTesting(context, info);
189 }
190
191 private void registerAuthority(String authority) {
192 mAuthority = authority;
193
194 mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
195 mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
196 mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
197 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
198 mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
199 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
200 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
201 mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
202 mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
203 }
204
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700205 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700206 * Test if a document is descendant (child, grandchild, etc) from the given
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700207 * parent. For example, providers must implement this to support
208 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
209 * requests to keep this request fast.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700210 *
211 * @param parentDocumentId parent to verify against.
212 * @param documentId child to verify.
213 * @return if given document is a descendant of the given parent.
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700214 * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700215 */
216 public boolean isChildDocument(String parentDocumentId, String documentId) {
217 return false;
218 }
219
220 /** {@hide} */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700221 private void enforceTree(Uri documentUri) {
222 if (isTreeUri(documentUri)) {
223 final String parent = getTreeDocumentId(documentUri);
224 final String child = getDocumentId(documentUri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700225 if (Objects.equals(parent, child)) {
226 return;
227 }
228 if (!isChildDocument(parent, child)) {
229 throw new SecurityException(
230 "Document " + child + " is not a descendant of " + parent);
231 }
232 }
233 }
234
235 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700236 * Create a new document and return its newly generated
Jeff Sharkey9352c382013-10-23 12:14:34 -0700237 * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700238 * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
239 * not change once returned.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700240 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700241 * @param parentDocumentId the parent directory to create the new document
242 * under.
243 * @param mimeType the concrete MIME type associated with the new document.
244 * If the MIME type is not supported, the provider must throw.
245 * @param displayName the display name of the new document. The provider may
246 * alter this name to meet any internal constraints, such as
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700247 * avoiding conflicting names.
Ben Lindf6d37e2017-03-20 18:51:20 -0700248
249 * @throws AuthenticationRequiredException If authentication is required from the user (such as
250 * login credentials), but it is not guaranteed that the client will handle this
251 * properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700252 */
253 @SuppressWarnings("unused")
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700254 public String createDocument(String parentDocumentId, String mimeType, String displayName)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700255 throws FileNotFoundException {
256 throw new UnsupportedOperationException("Create not supported");
257 }
258
259 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700260 * Rename an existing document.
261 * <p>
262 * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
263 * represent the renamed document, generate and return it. Any outstanding
264 * URI permission grants will be updated to point at the new document. If
265 * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
266 * rename, return {@code null}.
267 *
268 * @param documentId the document to rename.
269 * @param displayName the updated display name of the document. The provider
270 * may alter this name to meet any internal constraints, such as
271 * avoiding conflicting names.
Ben Lindf6d37e2017-03-20 18:51:20 -0700272 * @throws AuthenticationRequiredException If authentication is required from
273 * the user (such as login credentials), but it is not guaranteed
274 * that the client will handle this properly.
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700275 */
276 @SuppressWarnings("unused")
277 public String renameDocument(String documentId, String displayName)
278 throws FileNotFoundException {
279 throw new UnsupportedOperationException("Rename not supported");
280 }
281
282 /**
283 * Delete the requested document.
284 * <p>
285 * Upon returning, any URI permission grants for the given document will be
286 * revoked. If additional documents were deleted as a side effect of this
287 * call (such as documents inside a directory) the implementor is
288 * responsible for revoking those permissions using
289 * {@link #revokeDocumentPermission(String)}.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700290 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700291 * @param documentId the document to delete.
Ben Lindf6d37e2017-03-20 18:51:20 -0700292 * @throws AuthenticationRequiredException If authentication is required from
293 * the user (such as login credentials), but it is not guaranteed
294 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700295 */
296 @SuppressWarnings("unused")
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700297 public void deleteDocument(String documentId) throws FileNotFoundException {
298 throw new UnsupportedOperationException("Delete not supported");
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700299 }
300
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700301 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700302 * Copy the requested document or a document tree.
303 * <p>
304 * Copies a document including all child documents to another location within
305 * the same document provider. Upon completion returns the document id of
306 * the copied document at the target destination. {@code null} must never
307 * be returned.
308 *
309 * @param sourceDocumentId the document to copy.
310 * @param targetParentDocumentId the target document to be copied into as a child.
Ben Lindf6d37e2017-03-20 18:51:20 -0700311 * @throws AuthenticationRequiredException If authentication is required from
312 * the user (such as login credentials), but it is not guaranteed
313 * that the client will handle this properly.
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700314 */
315 @SuppressWarnings("unused")
316 public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
317 throws FileNotFoundException {
318 throw new UnsupportedOperationException("Copy not supported");
319 }
320
321 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900322 * Move the requested document or a document tree.
Tomasz Mikolajewskieeb8b602016-01-28 13:00:12 +0900323 *
324 * <p>Moves a document including all child documents to another location within
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900325 * the same document provider. Upon completion returns the document id of
326 * the copied document at the target destination. {@code null} must never
327 * be returned.
328 *
Tomasz Mikolajewskieeb8b602016-01-28 13:00:12 +0900329 * <p>It's the responsibility of the provider to revoke grants if the document
330 * is no longer accessible using <code>sourceDocumentId</code>.
331 *
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900332 * @param sourceDocumentId the document to move.
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900333 * @param sourceParentDocumentId the parent of the document to move.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900334 * @param targetParentDocumentId the target document to be a new parent of the
335 * source document.
Ben Lindf6d37e2017-03-20 18:51:20 -0700336 * @throws AuthenticationRequiredException If authentication is required from
337 * the user (such as login credentials), but it is not guaranteed
338 * that the client will handle this properly.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900339 */
340 @SuppressWarnings("unused")
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900341 public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
342 String targetParentDocumentId)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900343 throws FileNotFoundException {
344 throw new UnsupportedOperationException("Move not supported");
345 }
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900346
347 /**
348 * Removes the requested document or a document tree.
349 *
350 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
351 * This method is especially useful if the document can be in multiple parents.
352 *
353 * <p>It's the responsibility of the provider to revoke grants if the document is
354 * removed from the last parent, and effectively the document is deleted.
355 *
356 * @param documentId the document to remove.
357 * @param parentDocumentId the parent of the document to move.
Ben Lindf6d37e2017-03-20 18:51:20 -0700358 * @throws AuthenticationRequiredException If authentication is required from
359 * the user (such as login credentials), but it is not guaranteed
360 * that the client will handle this properly.
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900361 */
362 @SuppressWarnings("unused")
Tomasz Mikolajewski30714012016-02-15 17:07:37 +0900363 public void removeDocument(String documentId, String parentDocumentId)
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900364 throws FileNotFoundException {
365 throw new UnsupportedOperationException("Remove not supported");
366 }
367
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900368 /**
Garfield Tan06940e12016-10-07 16:03:17 -0700369 * Finds the canonical path for the requested document. The path must start
370 * from the parent document if parentDocumentId is not null or the root document
371 * if parentDocumentId is null. If there are more than one path to this document,
372 * return the most typical one. Include both the parent document or root document
373 * and the requested document in the returned path.
Garfield Tanaba97f32016-10-06 17:34:19 +0000374 *
Garfield Tan06940e12016-10-07 16:03:17 -0700375 * <p>This API assumes that document ID has enough info to infer the root.
376 * Different roots should use different document ID to refer to the same
Garfield Tanaba97f32016-10-06 17:34:19 +0000377 * document.
Ben Lin8ea82002017-03-08 17:30:16 -0800378 *
Garfield Tanaba97f32016-10-06 17:34:19 +0000379 *
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700380 * @param parentDocumentId the document from which the path starts if not null,
381 * or null to indicate a path from the root is requested.
Garfield Tanb690b4d2017-03-01 16:05:23 -0800382 * @param childDocumentId the document which path is requested.
Garfield Tan06940e12016-10-07 16:03:17 -0700383 * @return the path of the requested document. If parentDocumentId is null
384 * returned root ID must not be null. If parentDocumentId is not null
385 * returned root ID must be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700386 * @throws AuthenticationRequiredException If authentication is required from
387 * the user (such as login credentials), but it is not guaranteed
388 * that the client will handle this properly.
Garfield Tanaba97f32016-10-06 17:34:19 +0000389 */
Garfield Tanb690b4d2017-03-01 16:05:23 -0800390 public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
Garfield Tanaba97f32016-10-06 17:34:19 +0000391 throws FileNotFoundException {
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700392 throw new UnsupportedOperationException("findDocumentPath not supported.");
Garfield Tanaba97f32016-10-06 17:34:19 +0000393 }
394
395 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900396 * Creates an intent sender for a web link, if the document is web linkable.
397 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700398 * {@link AuthenticationRequiredException} can be thrown if user does not have
Ben Lin8ea82002017-03-08 17:30:16 -0800399 * sufficient permission for the linked document. Before any new permissions
400 * are granted for the linked document, a visible UI must be shown, so the
401 * user can explicitly confirm whether the permission grants are expected.
402 * The user must be able to cancel the operation.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900403 * <p>
404 * Options passed as an argument may include a list of recipients, such
405 * as email addresses. The provider should reflect these options if possible,
406 * but it's acceptable to ignore them. In either case, confirmation UI must
407 * be shown before any new permission grants are granted.
408 * <p>
409 * It is all right to generate a web link without granting new permissions,
410 * if opening the link would result in a page for requesting permission
411 * access. If it's impossible then the operation must fail by throwing an exception.
412 *
413 * @param documentId the document to create a web link intent for.
414 * @param options additional information, such as list of recipients. Optional.
Ben Lindf6d37e2017-03-20 18:51:20 -0700415 * @throws AuthenticationRequiredException If authentication is required from
416 * the user (such as login credentials), but it is not guaranteed
417 * that the client will handle this properly.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900418 *
419 * @see DocumentsContract.Document#FLAG_WEB_LINKABLE
420 * @see android.app.PendingIntent#getIntentSender
421 */
422 public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options)
423 throws FileNotFoundException {
424 throw new UnsupportedOperationException("createWebLink is not supported.");
425 }
426
427 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700428 * Return all roots currently provided. To display to users, you must define
429 * at least one root. You should avoid making network requests to keep this
430 * request fast.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700431 * <p>
432 * Each root is defined by the metadata columns described in {@link Root},
433 * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
434 * representing a tree of documents to display under that root.
435 * <p>
436 * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
Jeff Sharkey9352c382013-10-23 12:14:34 -0700437 * android.database.ContentObserver, boolean)} with
438 * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
Ben Lin8ea82002017-03-08 17:30:16 -0800439 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700440 *
441 * @param projection list of {@link Root} columns to put into the cursor. If
442 * {@code null} all supported columns should be included.
443 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700444 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
445
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700446 /**
447 * Return recently modified documents under the requested root. This will
448 * only be called for roots that advertise
449 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
450 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
451 * limited to only return the 64 most recently modified documents.
Jeff Sharkey37ed78e2013-11-05 12:38:21 -0800452 * <p>
453 * Recent documents do not support change notifications.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700454 *
455 * @param projection list of {@link Document} columns to put into the
456 * cursor. If {@code null} all supported columns should be
457 * included.
458 * @see DocumentsContract#EXTRA_LOADING
459 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700460 @SuppressWarnings("unused")
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700461 public Cursor queryRecentDocuments(String rootId, String[] projection)
462 throws FileNotFoundException {
463 throw new UnsupportedOperationException("Recent not supported");
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700464 }
465
466 /**
Risan6a4a8f62018-10-30 17:57:56 -0600467 * Return recently modified documents under the requested root. This will
468 * only be called for roots that advertise
469 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
470 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order of
471 * the most recently modified documents.
472 * <p>
473 * If this method is overriden by the concrete DocumentsProvider and
Jeff Sharkeyfd66ed02019-03-08 15:09:08 -0700474 * {@link ContentResolver#QUERY_ARG_LIMIT} is specified with a nonnegative
475 * int under queryArgs, the result will be limited by that number and
476 * {@link ContentResolver#QUERY_ARG_LIMIT} will be specified under
477 * {@link ContentResolver#EXTRA_HONORED_ARGS}. Otherwise, a default 64 limit
478 * will be used and no QUERY_ARG* will be specified under
479 * {@link ContentResolver#EXTRA_HONORED_ARGS}.
Risan6a4a8f62018-10-30 17:57:56 -0600480 * <p>
481 * Recent documents do not support change notifications.
482 *
483 * @param projection list of {@link Document} columns to put into the
484 * cursor. If {@code null} all supported columns should be
485 * included.
486 * @param queryArgs the extra query arguments.
487 * @param signal used by the caller to signal if the request should be
488 * cancelled. May be null.
489 * @see DocumentsContract#EXTRA_LOADING
490 */
491 @SuppressWarnings("unused")
Ivan Chiangffba1552019-03-04 16:59:39 +0800492 @Nullable
Risan6a4a8f62018-10-30 17:57:56 -0600493 public Cursor queryRecentDocuments(
Ivan Chiangffba1552019-03-04 16:59:39 +0800494 @NonNull String rootId, @Nullable String[] projection, @Nullable Bundle queryArgs,
495 @Nullable CancellationSignal signal) throws FileNotFoundException {
496 Preconditions.checkNotNull(rootId, "rootId can not be null");
497
Risan6a4a8f62018-10-30 17:57:56 -0600498 Cursor c = queryRecentDocuments(rootId, projection);
499 Bundle extras = new Bundle();
500 c.setExtras(extras);
501 extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[0]);
502 return c;
503 }
504
505 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700506 * Return metadata for the single requested document. You should avoid
507 * making network requests to keep this request fast.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700508 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700509 * @param documentId the document to return.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700510 * @param projection list of {@link Document} columns to put into the
511 * cursor. If {@code null} all supported columns should be
512 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700513 * @throws AuthenticationRequiredException If authentication is required from
514 * the user (such as login credentials), but it is not guaranteed
515 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700516 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700517 public abstract Cursor queryDocument(String documentId, String[] projection)
518 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700519
520 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700521 * Return the children documents contained in the requested directory. This
522 * must only return immediate descendants, as additional queries will be
523 * issued to recursively explore the tree.
524 * <p>
Steve McKay29c3f682016-12-16 14:52:59 -0800525 * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
526 * should override {@link #queryChildDocuments(String, String[], Bundle)}.
527 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700528 * If your provider is cloud-based, and you have some data cached or pinned
529 * locally, you may return the local data immediately, setting
530 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700531 * you are still fetching additional data. Then, when the network data is
532 * available, you can send a change notification to trigger a requery and
Jeff Sharkey3b945402013-11-13 13:31:09 -0800533 * return the complete contents. To return a Cursor with extras, you need to
534 * extend and override {@link Cursor#getExtras()}.
Jeff Sharkey9352c382013-10-23 12:14:34 -0700535 * <p>
536 * To support change notifications, you must
537 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
538 * Uri, such as
539 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
540 * you can call {@link ContentResolver#notifyChange(Uri,
541 * android.database.ContentObserver, boolean)} with that Uri to send change
542 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700543 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700544 * @param parentDocumentId the directory to return children for.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700545 * @param projection list of {@link Document} columns to put into the
546 * cursor. If {@code null} all supported columns should be
547 * included.
548 * @param sortOrder how to order the rows, formatted as an SQL
549 * {@code ORDER BY} clause (excluding the ORDER BY itself).
550 * Passing {@code null} will use the default sort order, which
551 * may be unordered. This ordering is a hint that can be used to
552 * prioritize how data is fetched from the network, but UI may
553 * always enforce a specific ordering.
Ben Lindf6d37e2017-03-20 18:51:20 -0700554 * @throws AuthenticationRequiredException If authentication is required from
555 * the user (such as login credentials), but it is not guaranteed
556 * that the client will handle this properly.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700557 * @see DocumentsContract#EXTRA_LOADING
558 * @see DocumentsContract#EXTRA_INFO
559 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700560 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700561 public abstract Cursor queryChildDocuments(
562 String parentDocumentId, String[] projection, String sortOrder)
563 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700564
Steve McKay29c3f682016-12-16 14:52:59 -0800565 /**
566 * Override this method to return the children documents contained
567 * in the requested directory. This must return immediate descendants only.
568 *
569 * <p>If your provider is cloud-based, and you have data cached
570 * locally, you may return the local data immediately, setting
571 * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that
572 * you are still fetching additional data. Then, when the network data is
573 * available, you can send a change notification to trigger a requery and
574 * return the complete contents. To return a Cursor with extras, you need to
575 * extend and override {@link Cursor#getExtras()}.
576 *
577 * <p>To support change notifications, you must
578 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
579 * Uri, such as
580 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
581 * you can call {@link ContentResolver#notifyChange(Uri,
582 * android.database.ContentObserver, boolean)} with that Uri to send change
583 * notifications.
584 *
585 * @param parentDocumentId the directory to return children for.
586 * @param projection list of {@link Document} columns to put into the
587 * cursor. If {@code null} all supported columns should be
588 * included.
589 * @param queryArgs Bundle containing sorting information or other
590 * argument useful to the provider. If no sorting
591 * information is available, default sorting
592 * will be used, which may be unordered. See
593 * {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
594 * details.
Ben Lindf6d37e2017-03-20 18:51:20 -0700595 * @throws AuthenticationRequiredException If authentication is required from
596 * the user (such as login credentials), but it is not guaranteed
597 * that the client will handle this properly.
Steve McKay29c3f682016-12-16 14:52:59 -0800598 *
599 * @see DocumentsContract#EXTRA_LOADING
600 * @see DocumentsContract#EXTRA_INFO
601 * @see DocumentsContract#EXTRA_ERROR
602 */
603 public Cursor queryChildDocuments(
604 String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
605 throws FileNotFoundException {
606
607 return queryChildDocuments(
608 parentDocumentId, projection, getSortClause(queryArgs));
609 }
610
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700611 /** {@hide} */
612 @SuppressWarnings("unused")
613 public Cursor queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800614 String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700615 throws FileNotFoundException {
616 throw new UnsupportedOperationException("Manage not supported");
617 }
618
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700619 /**
Mark Dolinerd0646dc2014-08-27 16:04:02 -0700620 * Return documents that match the given query under the requested
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700621 * root. The returned documents should be sorted by relevance in descending
622 * order. How documents are matched against the query string is an
623 * implementation detail left to each provider, but it's suggested that at
624 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
625 * case-insensitive fashion.
626 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700627 * If your provider is cloud-based, and you have some data cached or pinned
628 * locally, you may return the local data immediately, setting
629 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
630 * you are still fetching additional data. Then, when the network data is
631 * available, you can send a change notification to trigger a requery and
632 * return the complete contents.
633 * <p>
634 * To support change notifications, you must
635 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
636 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
637 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
638 * android.database.ContentObserver, boolean)} with that Uri to send change
639 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700640 *
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700641 * @param rootId the root to search under.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700642 * @param query string to match documents against.
643 * @param projection list of {@link Document} columns to put into the
644 * cursor. If {@code null} all supported columns should be
645 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700646 * @throws AuthenticationRequiredException If authentication is required from
647 * the user (such as login credentials), but it is not guaranteed
648 * that the client will handle this properly.
649 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700650 * @see DocumentsContract#EXTRA_LOADING
651 * @see DocumentsContract#EXTRA_INFO
652 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700653 */
654 @SuppressWarnings("unused")
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700655 public Cursor querySearchDocuments(String rootId, String query, String[] projection)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700656 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700657 throw new UnsupportedOperationException("Search not supported");
658 }
659
Garfield Tan87877032017-03-22 12:01:14 -0700660 /**
Ivan Chianga972d042018-10-15 15:23:02 +0800661 * Return documents that match the given query under the requested
662 * root. The returned documents should be sorted by relevance in descending
663 * order. How documents are matched against the query string is an
664 * implementation detail left to each provider, but it's suggested that at
665 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
666 * case-insensitive fashion.
667 * <p>
668 * If your provider is cloud-based, and you have some data cached or pinned
669 * locally, you may return the local data immediately, setting
670 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
671 * you are still fetching additional data. Then, when the network data is
672 * available, you can send a change notification to trigger a requery and
673 * return the complete contents.
674 * <p>
675 * To support change notifications, you must
676 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
677 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
678 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
679 * android.database.ContentObserver, boolean)} with that Uri to send change
680 * notifications.
681 *
682 * @param rootId the root to search under.
683 * @param projection list of {@link Document} columns to put into the
684 * cursor. If {@code null} all supported columns should be
685 * included.
686 * @param queryArgs the query arguments.
Ivan Chiang1edfcb22018-11-14 13:20:22 +0800687 * {@link DocumentsContract#QUERY_ARG_EXCLUDE_MEDIA},
Ivan Chianga972d042018-10-15 15:23:02 +0800688 * {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME},
689 * {@link DocumentsContract#QUERY_ARG_MIME_TYPES},
690 * {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER},
691 * {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}.
692 * @return cursor containing search result. Include
693 * {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor}
694 * extras {@link Bundle} when any QUERY_ARG_* value was honored
695 * during the preparation of the results.
696 *
Ivan Chiangc26d3c22019-01-10 19:29:01 +0800697 * @see Root#COLUMN_QUERY_ARGS
Ivan Chianga972d042018-10-15 15:23:02 +0800698 * @see ContentResolver#EXTRA_HONORED_ARGS
699 * @see DocumentsContract#EXTRA_LOADING
700 * @see DocumentsContract#EXTRA_INFO
701 * @see DocumentsContract#EXTRA_ERROR
Ivan Chianga972d042018-10-15 15:23:02 +0800702 */
703 @SuppressWarnings("unused")
Ivan Chiangffba1552019-03-04 16:59:39 +0800704 @Nullable
705 public Cursor querySearchDocuments(@NonNull String rootId,
706 @Nullable String[] projection, @NonNull Bundle queryArgs) throws FileNotFoundException {
Ivan Chiang857a2222019-01-09 15:24:21 +0800707 Preconditions.checkNotNull(rootId, "rootId can not be null");
708 Preconditions.checkNotNull(queryArgs, "queryArgs can not be null");
Ivan Chianga972d042018-10-15 15:23:02 +0800709 return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs),
710 projection);
711 }
712
713 /**
Garfield Tan87877032017-03-22 12:01:14 -0700714 * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
715 *
716 * @param rootId the root to be ejected.
717 * @see Root#FLAG_SUPPORTS_EJECT
718 */
Ben Line7822fb2016-06-24 15:21:08 -0700719 @SuppressWarnings("unused")
Garfield Tan87877032017-03-22 12:01:14 -0700720 public void ejectRoot(String rootId) {
Ben Line7822fb2016-06-24 15:21:08 -0700721 throw new UnsupportedOperationException("Eject not supported");
722 }
723
Ivan Chiangb26b09f2018-11-28 18:20:32 +0800724 /**
725 * Returns metadata associated with the document. The type of metadata returned
726 * is specific to the document type. For example the data returned for an image
727 * file will likely consist primarily or solely of EXIF metadata.
728 *
729 * <p>The returned {@link Bundle} will contain zero or more entries depending
730 * on the type of data supported by the document provider.
731 *
732 * <ol>
733 * <li>A {@link DocumentsContract#METADATA_TYPES} containing a {@code String[]} value.
734 * The string array identifies the type or types of metadata returned. Each
735 * value in the can be used to access a {@link Bundle} of data
736 * containing that type of data.
737 * <li>An entry each for each type of returned metadata. Each set of metadata is
738 * itself represented as a bundle and accessible via a string key naming
739 * the type of data.
740 * </ol>
741 *
742 * @param documentId get the metadata of the document
743 * @return a Bundle of Bundles.
744 * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri)
745 */
Ivan Chiang857a2222019-01-09 15:24:21 +0800746 public @Nullable Bundle getDocumentMetadata(@NonNull String documentId)
Julian Mancinib6505152017-06-27 13:29:09 -0700747 throws FileNotFoundException {
748 throw new UnsupportedOperationException("Metadata not supported");
749 }
750
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700751 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700752 * Return concrete MIME type of the requested document. Must match the value
753 * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
754 * implementation queries {@link #queryDocument(String, String[])}, so
755 * providers may choose to override this as an optimization.
Ben Lin8ea82002017-03-08 17:30:16 -0800756 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700757 * @throws AuthenticationRequiredException If authentication is required from
758 * the user (such as login credentials), but it is not guaranteed
759 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700760 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700761 public String getDocumentType(String documentId) throws FileNotFoundException {
762 final Cursor cursor = queryDocument(documentId, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700763 try {
764 if (cursor.moveToFirst()) {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700765 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700766 } else {
767 return null;
768 }
769 } finally {
770 IoUtils.closeQuietly(cursor);
771 }
772 }
773
774 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700775 * Open and return the requested document.
776 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700777 * Your provider should return a reliable {@link ParcelFileDescriptor} to
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700778 * detect when the remote caller has finished reading or writing the
Garfield Tanaf03e5a2017-05-15 14:19:11 -0700779 * document.
780 * <p>
781 * Mode "r" should always be supported. Provider should throw
782 * {@link UnsupportedOperationException} if the passing mode is not supported.
783 * You may return a pipe or socket pair if the mode is exclusively "r" or
784 * "w", but complex modes like "rw" imply a normal file on disk that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700785 * supports seeking.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700786 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700787 * If you block while downloading content, you should periodically check
788 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700789 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700790 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700791 * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
792 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800793 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700794 * @throws AuthenticationRequiredException If authentication is required from
795 * the user (such as login credentials), but it is not guaranteed
796 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700797 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
798 * OnCloseListener)
799 * @see ParcelFileDescriptor#createReliablePipe()
800 * @see ParcelFileDescriptor#createReliableSocketPair()
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700801 * @see ParcelFileDescriptor#parseMode(String)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700802 */
803 public abstract ParcelFileDescriptor openDocument(
Steve McKay36f1d7e2017-07-20 11:41:58 -0700804 String documentId,
805 String mode,
806 @Nullable CancellationSignal signal) throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700807
808 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700809 * Open and return a thumbnail of the requested document.
810 * <p>
811 * A provider should return a thumbnail closely matching the hinted size,
812 * attempting to serve from a local cache if possible. A provider should
813 * never return images more than double the hinted size.
814 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700815 * If you perform expensive operations to download or generate a thumbnail,
816 * you should periodically check {@link CancellationSignal#isCanceled()} to
817 * abort abandoned thumbnail requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700818 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700819 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700820 * @param sizeHint hint of the optimal thumbnail dimensions.
821 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800822 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700823 * @throws AuthenticationRequiredException If authentication is required from
824 * the user (such as login credentials), but it is not guaranteed
825 * that the client will handle this properly.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700826 * @see Document#FLAG_SUPPORTS_THUMBNAIL
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700827 */
828 @SuppressWarnings("unused")
829 public AssetFileDescriptor openDocumentThumbnail(
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700830 String documentId, Point sizeHint, CancellationSignal signal)
831 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700832 throw new UnsupportedOperationException("Thumbnails not supported");
833 }
834
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700835 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900836 * Open and return the document in a format matching the specified MIME
837 * type filter.
838 * <p>
839 * A provider may perform a conversion if the documents's MIME type is not
840 * matching the specified MIME type filter.
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +0900841 * <p>
842 * Virtual documents must have at least one streamable format.
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900843 *
844 * @param documentId the document to return.
845 * @param mimeTypeFilter the MIME type filter for the requested format. May
846 * be *\/*, which matches any MIME type.
847 * @param opts extra options from the client. Specific to the content
848 * provider.
849 * @param signal used by the caller to signal if the request should be
850 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700851 * @throws AuthenticationRequiredException If authentication is required from
852 * the user (such as login credentials), but it is not guaranteed
853 * that the client will handle this properly.
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +0900854 * @see #getDocumentStreamTypes(String, String)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900855 */
856 @SuppressWarnings("unused")
857 public AssetFileDescriptor openTypedDocument(
858 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
859 throws FileNotFoundException {
Tomasz Mikolajewski75395652016-01-07 07:19:22 +0000860 throw new FileNotFoundException("The requested MIME type is not supported.");
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900861 }
862
Steve McKay29c3f682016-12-16 14:52:59 -0800863 @Override
864 public final Cursor query(Uri uri, String[] projection, String selection,
865 String[] selectionArgs, String sortOrder) {
866 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
867 // transport method. We override that, and don't ever delegate to this method.
868 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
869 }
870
Steve McKay0bb25292017-01-24 11:45:18 -0800871 /**
872 * WARNING: Sub-classes should not override this method. This method is non-final
873 * solely for the purposes of backwards compatibility.
874 *
875 * @see #queryChildDocuments(String, String[], Bundle),
876 * {@link #queryDocument(String, String[])},
877 * {@link #queryRecentDocuments(String, String[])},
878 * {@link #queryRoots(String[])}, and
Ivan Chianga972d042018-10-15 15:23:02 +0800879 * {@link #querySearchDocuments(String, String[], Bundle)}.
Steve McKay0bb25292017-01-24 11:45:18 -0800880 */
Steve McKay29c3f682016-12-16 14:52:59 -0800881 @Override
Steve McKay0bb25292017-01-24 11:45:18 -0800882 public Cursor query(Uri uri, String[] projection, String selection,
Steve McKay29c3f682016-12-16 14:52:59 -0800883 String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
884 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
885 // transport method. We override that, and don't ever delegate to this metohd.
886 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
887 }
888
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900889 /**
koprivadebd4ee2018-09-13 10:59:46 -0700890 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700891 *
892 * @see #queryRoots(String[])
Risan6a4a8f62018-10-30 17:57:56 -0600893 * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700894 * @see #queryDocument(String, String[])
895 * @see #queryChildDocuments(String, String[], String)
Ivan Chianga972d042018-10-15 15:23:02 +0800896 * @see #querySearchDocuments(String, String[], Bundle)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700897 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700898 @Override
Steve McKay29c3f682016-12-16 14:52:59 -0800899 public final Cursor query(
900 Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700901 try {
902 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700903 case MATCH_ROOTS:
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700904 return queryRoots(projection);
905 case MATCH_RECENT:
Risan6a4a8f62018-10-30 17:57:56 -0600906 return queryRecentDocuments(
907 getRootId(uri), projection, queryArgs, cancellationSignal);
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700908 case MATCH_SEARCH:
Ivan Chianga972d042018-10-15 15:23:02 +0800909 return querySearchDocuments(getRootId(uri), projection, queryArgs);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700910 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700911 case MATCH_DOCUMENT_TREE:
912 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700913 return queryDocument(getDocumentId(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700914 case MATCH_CHILDREN:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700915 case MATCH_CHILDREN_TREE:
916 enforceTree(uri);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700917 if (DocumentsContract.isManageMode(uri)) {
Steve McKay29c3f682016-12-16 14:52:59 -0800918 // TODO: Update "ForManage" variant to support query args.
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700919 return queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800920 getDocumentId(uri),
921 projection,
922 getSortClause(queryArgs));
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700923 } else {
Steve McKay29c3f682016-12-16 14:52:59 -0800924 return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700925 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700926 default:
927 throw new UnsupportedOperationException("Unsupported Uri " + uri);
928 }
929 } catch (FileNotFoundException e) {
930 Log.w(TAG, "Failed during query", e);
931 return null;
932 }
933 }
934
Steve McKay29c3f682016-12-16 14:52:59 -0800935 private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
936 queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
937 String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
938
939 if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
940 sortClause = ContentResolver.createSqlSortClause(queryArgs);
941 }
942
943 return sortClause;
944 }
945
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700946 /**
koprivadebd4ee2018-09-13 10:59:46 -0700947 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700948 *
949 * @see #getDocumentType(String)
950 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700951 @Override
952 public final String getType(Uri uri) {
953 try {
954 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700955 case MATCH_ROOT:
956 return DocumentsContract.Root.MIME_TYPE_ITEM;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700957 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700958 case MATCH_DOCUMENT_TREE:
959 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700960 return getDocumentType(getDocumentId(uri));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700961 default:
962 return null;
963 }
964 } catch (FileNotFoundException e) {
965 Log.w(TAG, "Failed during getType", e);
966 return null;
967 }
968 }
969
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700970 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700971 * Implementation is provided by the parent class. Can be overridden to
972 * provide additional functionality, but subclasses <em>must</em> always
973 * call the superclass. If the superclass returns {@code null}, the subclass
974 * may implement custom behavior.
975 * <p>
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700976 * This is typically used to resolve a subtree URI into a concrete document
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700977 * reference, issuing a narrower single-document URI permission grant along
978 * the way.
979 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700980 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700981 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800982 @CallSuper
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700983 @Override
984 public Uri canonicalize(Uri uri) {
985 final Context context = getContext();
986 switch (mMatcher.match(uri)) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700987 case MATCH_DOCUMENT_TREE:
988 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700989
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700990 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700991
992 // Caller may only have prefix grant, so extend them a grant to
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700993 // the narrow URI.
994 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700995 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
996 return narrowUri;
997 }
998 return null;
999 }
1000
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001001 private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
1002 // TODO: move this to a direct AMS call
1003 int modeFlags = 0;
1004 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
1005 == PackageManager.PERMISSION_GRANTED) {
1006 modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
1007 }
1008 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
1009 == PackageManager.PERMISSION_GRANTED) {
1010 modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
1011 }
1012 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
1013 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
1014 == PackageManager.PERMISSION_GRANTED) {
1015 modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
1016 }
1017 return modeFlags;
1018 }
1019
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001020 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001021 * Implementation is provided by the parent class. Throws by default, and
koprivadebd4ee2018-09-13 10:59:46 -07001022 * cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001023 *
1024 * @see #createDocument(String, String, String)
1025 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001026 @Override
1027 public final Uri insert(Uri uri, ContentValues values) {
1028 throw new UnsupportedOperationException("Insert not supported");
1029 }
1030
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001031 /**
1032 * Implementation is provided by the parent class. Throws by default, and
koprivadebd4ee2018-09-13 10:59:46 -07001033 * cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001034 *
1035 * @see #deleteDocument(String)
1036 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001037 @Override
1038 public final int delete(Uri uri, String selection, String[] selectionArgs) {
1039 throw new UnsupportedOperationException("Delete not supported");
1040 }
1041
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001042 /**
1043 * Implementation is provided by the parent class. Throws by default, and
koprivadebd4ee2018-09-13 10:59:46 -07001044 * cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001045 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001046 @Override
1047 public final int update(
1048 Uri uri, ContentValues values, String selection, String[] selectionArgs) {
1049 throw new UnsupportedOperationException("Update not supported");
1050 }
1051
Jeff Sharkey911d7f42013-09-05 18:11:45 -07001052 /**
1053 * Implementation is provided by the parent class. Can be overridden to
1054 * provide additional functionality, but subclasses <em>must</em> always
1055 * call the superclass. If the superclass returns {@code null}, the subclass
1056 * may implement custom behavior.
Jeff Sharkey911d7f42013-09-05 18:11:45 -07001057 */
Tor Norbyec615c6f2015-03-02 10:11:44 -08001058 @CallSuper
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001059 @Override
Jeff Sharkey911d7f42013-09-05 18:11:45 -07001060 public Bundle call(String method, String arg, Bundle extras) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001061 if (!method.startsWith("android:")) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001062 // Ignore non-platform methods
Jeff Sharkey911d7f42013-09-05 18:11:45 -07001063 return super.call(method, arg, extras);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001064 }
1065
Steve McKayd3afdee2015-11-19 17:27:12 -08001066 try {
1067 return callUnchecked(method, arg, extras);
1068 } catch (FileNotFoundException e) {
Ben Lin8ea82002017-03-08 17:30:16 -08001069 throw new ParcelableException(e);
Steve McKayd3afdee2015-11-19 17:27:12 -08001070 }
1071 }
1072
1073 private Bundle callUnchecked(String method, String arg, Bundle extras)
1074 throws FileNotFoundException {
1075
Jeff Sharkeyb7e12552014-05-21 22:22:03 -07001076 final Context context = getContext();
Ben Lind7d14872016-06-28 17:12:52 -07001077 final Bundle out = new Bundle();
1078
1079 if (METHOD_EJECT_ROOT.equals(method)) {
1080 // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
1081 // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
Garfield Tan87877032017-03-22 12:01:14 -07001082 // MANAGE_DOCUMENTS or associated URI permission here instead
Ben Lind7d14872016-06-28 17:12:52 -07001083 final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
Garfield Tan87877032017-03-22 12:01:14 -07001084 enforceWritePermissionInner(rootUri, getCallingPackage(), null);
Ben Lind7d14872016-06-28 17:12:52 -07001085
Garfield Tan87877032017-03-22 12:01:14 -07001086 final String rootId = DocumentsContract.getRootId(rootUri);
1087 ejectRoot(rootId);
Ben Lind7d14872016-06-28 17:12:52 -07001088
1089 return out;
1090 }
1091
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001092 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
1093 final String authority = documentUri.getAuthority();
1094 final String documentId = DocumentsContract.getDocumentId(documentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -07001095
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001096 if (!mAuthority.equals(authority)) {
1097 throw new SecurityException(
1098 "Requested authority " + authority + " doesn't match provider " + mAuthority);
1099 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001100
Steve McKayd3afdee2015-11-19 17:27:12 -08001101 // If the URI is a tree URI performs some validation.
1102 enforceTree(documentUri);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001103
Steve McKayd3afdee2015-11-19 17:27:12 -08001104 if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
1105 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1106
1107 final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1108 final String childAuthority = childUri.getAuthority();
1109 final String childId = DocumentsContract.getDocumentId(childUri);
1110
1111 out.putBoolean(
1112 DocumentsContract.EXTRA_RESULT,
1113 mAuthority.equals(childAuthority)
1114 && isChildDocument(documentId, childId));
1115
1116 } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
1117 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1118
1119 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
1120 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1121 final String newDocumentId = createDocument(documentId, mimeType, displayName);
1122
1123 // No need to issue new grants here, since caller either has
1124 // manage permission or a prefix grant. We might generate a
1125 // tree style URI if that's how they called us.
1126 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1127 newDocumentId);
1128 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1129
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001130 } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
1131 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1132
1133 final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
1134 final IntentSender intentSender = createWebLinkIntent(documentId, options);
1135
1136 out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
1137
Steve McKayd3afdee2015-11-19 17:27:12 -08001138 } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
1139 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1140
1141 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1142 final String newDocumentId = renameDocument(documentId, displayName);
1143
1144 if (newDocumentId != null) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001145 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001146 newDocumentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001147
1148 // If caller came in with a narrow grant, issue them a
1149 // narrow grant for the newly renamed document.
1150 if (!isTreeUri(newDocumentUri)) {
1151 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1152 documentUri);
1153 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1154 }
1155
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001156 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -07001157
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001158 // Original document no longer exists, clean up any grants.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001159 revokeDocumentPermission(documentId);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001160 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001161
1162 } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
1163 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1164 deleteDocument(documentId);
1165
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001166 // Document no longer exists, clean up any grants.
Steve McKayd3afdee2015-11-19 17:27:12 -08001167 revokeDocumentPermission(documentId);
1168
1169 } else if (METHOD_COPY_DOCUMENT.equals(method)) {
1170 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1171 final String targetId = DocumentsContract.getDocumentId(targetUri);
1172
1173 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1174 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1175
1176 final String newDocumentId = copyDocument(documentId, targetId);
1177
1178 if (newDocumentId != null) {
1179 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1180 newDocumentId);
1181
1182 if (!isTreeUri(newDocumentUri)) {
1183 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1184 documentUri);
1185 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1186 }
1187
1188 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1189 }
1190
1191 } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001192 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1193 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
Steve McKayd3afdee2015-11-19 17:27:12 -08001194 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1195 final String targetId = DocumentsContract.getDocumentId(targetUri);
1196
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001197 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1198 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
Steve McKayd3afdee2015-11-19 17:27:12 -08001199 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1200
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001201 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001202
1203 if (newDocumentId != null) {
1204 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1205 newDocumentId);
1206
1207 if (!isTreeUri(newDocumentUri)) {
1208 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1209 documentUri);
1210 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1211 }
1212
1213 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1214 }
1215
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001216 } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
1217 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1218 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
1219
1220 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
1221 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1222 removeDocument(documentId, parentSourceId);
1223
1224 // It's responsibility of the provider to revoke any grants, as the document may be
1225 // still attached to another parents.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001226 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
Garfield Tan06940e12016-10-07 16:03:17 -07001227 final boolean isTreeUri = isTreeUri(documentUri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001228
Garfield Tan06940e12016-10-07 16:03:17 -07001229 if (isTreeUri) {
1230 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1231 } else {
1232 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
1233 }
1234
1235 final String parentDocumentId = isTreeUri
1236 ? DocumentsContract.getTreeDocumentId(documentUri)
1237 : null;
1238
Garfield Tanb690b4d2017-03-01 16:05:23 -08001239 Path path = findDocumentPath(parentDocumentId, documentId);
Garfield Tan06940e12016-10-07 16:03:17 -07001240
1241 // Ensure provider doesn't leak information to unprivileged callers.
Garfield Tan5f214802016-10-26 14:52:46 -07001242 if (isTreeUri) {
1243 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
1244 Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
1245 + parentDocumentId + " found: " + path.getPath().get(0));
Garfield Tanb690b4d2017-03-01 16:05:23 -08001246
1247 LinkedList<String> docs = new LinkedList<>(path.getPath());
1248 while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
1249 docs.removeFirst();
1250 }
1251 path = new Path(null, docs);
Garfield Tan5f214802016-10-26 14:52:46 -07001252 }
1253
1254 if (path.getRootId() != null) {
1255 Log.wtf(TAG, "Provider returns root id :"
1256 + path.getRootId() + " unexpectedly. Erase root id.");
1257 path = new Path(null, path.getPath());
1258 }
Garfield Tan06940e12016-10-07 16:03:17 -07001259 }
Garfield Tanaba97f32016-10-06 17:34:19 +00001260
1261 out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
Julian Mancinib6505152017-06-27 13:29:09 -07001262 } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
Steve McKay17a9ce32017-07-27 13:37:14 -07001263 return getDocumentMetadata(documentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001264 } else {
1265 throw new UnsupportedOperationException("Method not supported " + method);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001266 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001267
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001268 return out;
1269 }
1270
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001271 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001272 * Revoke any active permission grants for the given
1273 * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
1274 * becomes invalid. Follows the same semantics as
1275 * {@link Context#revokeUriPermission(Uri, int)}.
1276 */
1277 public final void revokeDocumentPermission(String documentId) {
1278 final Context context = getContext();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001279 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
1280 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001281 }
1282
1283 /**
koprivadebd4ee2018-09-13 10:59:46 -07001284 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001285 *
1286 * @see #openDocument(String, String, CancellationSignal)
1287 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001288 @Override
1289 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001290 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001291 return openDocument(getDocumentId(uri), mode, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001292 }
1293
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001294 /**
koprivadebd4ee2018-09-13 10:59:46 -07001295 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001296 *
1297 * @see #openDocument(String, String, CancellationSignal)
1298 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001299 @Override
1300 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
1301 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001302 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001303 return openDocument(getDocumentId(uri), mode, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001304 }
1305
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001306 /**
koprivadebd4ee2018-09-13 10:59:46 -07001307 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001308 *
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001309 * @see #openDocument(String, String, CancellationSignal)
1310 */
1311 @Override
1312 @SuppressWarnings("resource")
1313 public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
1314 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001315 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001316 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
1317 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1318 }
1319
1320 /**
koprivadebd4ee2018-09-13 10:59:46 -07001321 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001322 *
1323 * @see #openDocument(String, String, CancellationSignal)
1324 */
1325 @Override
1326 @SuppressWarnings("resource")
1327 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
1328 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001329 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001330 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
1331 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1332 }
1333
1334 /**
koprivadebd4ee2018-09-13 10:59:46 -07001335 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001336 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001337 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001338 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001339 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001340 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001341 @Override
1342 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
1343 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001344 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001345 }
1346
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001347 /**
koprivadebd4ee2018-09-13 10:59:46 -07001348 * Implementation is provided by the parent class. Cannot be overridden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001349 *
1350 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001351 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001352 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001353 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001354 @Override
1355 public final AssetFileDescriptor openTypedAssetFile(
1356 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1357 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001358 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
1359 }
1360
1361 /**
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001362 * Return a list of streamable MIME types matching the filter, which can be passed to
1363 * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
1364 *
1365 * <p>The default implementation returns a MIME type provided by
1366 * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
1367 * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
1368 *
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +09001369 * <p>Virtual documents must have at least one streamable format.
1370 *
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001371 * @see #getStreamTypes(Uri, String)
1372 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
1373 */
1374 public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
1375 Cursor cursor = null;
1376 try {
1377 cursor = queryDocument(documentId, null);
1378 if (cursor.moveToFirst()) {
1379 final String mimeType =
1380 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
1381 final long flags =
1382 cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
1383 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
Ivan Chianga972d042018-10-15 15:23:02 +08001384 MimeTypeFilter.matches(mimeType, mimeTypeFilter)) {
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001385 return new String[] { mimeType };
1386 }
1387 }
1388 } catch (FileNotFoundException e) {
1389 return null;
1390 } finally {
1391 IoUtils.closeQuietly(cursor);
1392 }
1393
1394 // No streamable MIME types.
1395 return null;
1396 }
1397
1398 /**
1399 * Called by a client to determine the types of data streams that this content provider
1400 * support for the given URI.
1401 *
1402 * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
1403 *
1404 * @see #getDocumentStreamTypes(String, String)
1405 */
1406 @Override
1407 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
1408 enforceTree(uri);
1409 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
1410 }
1411
1412 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001413 * @hide
1414 */
1415 private final AssetFileDescriptor openTypedAssetFileImpl(
1416 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1417 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001418 enforceTree(uri);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001419 final String documentId = getDocumentId(uri);
Jeff Sharkey5b836f22014-08-27 14:46:32 -07001420 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
1421 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001422 return openDocumentThumbnail(documentId, sizeHint, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001423 }
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001424 if ("*/*".equals(mimeTypeFilter)) {
1425 // If they can take anything, the untyped open call is good enough.
1426 return openAssetFile(uri, "r");
1427 }
1428 final String baseType = getType(uri);
1429 if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
1430 // Use old untyped open call if this provider has a type for this
1431 // URI and it matches the request.
1432 return openAssetFile(uri, "r");
1433 }
1434 // For any other yet unhandled case, let the provider subclass handle it.
1435 return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001436 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001437}