blob: 81b1921dd809085bfad2dc1162f5d419423ee666 [file] [log] [blame]
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.provider;
18
Steve McKayd3afdee2015-11-19 17:27:12 -080019import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070020import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT;
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +090021import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070022import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT;
Ben Line7822fb2016-06-24 15:21:08 -070023import static android.provider.DocumentsContract.METHOD_EJECT_ROOT;
Garfield Tan3f6b68a2016-11-01 14:13:38 -070024import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH;
Julian Mancinib6505152017-06-27 13:29:09 -070025import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA;
Steve McKayd3afdee2015-11-19 17:27:12 -080026import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT;
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +090027import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT;
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +090028import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT;
Steve McKayd3afdee2015-11-19 17:27:12 -080029import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070030import static android.provider.DocumentsContract.buildDocumentUri;
31import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree;
32import static android.provider.DocumentsContract.buildTreeDocumentUri;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070033import static android.provider.DocumentsContract.getDocumentId;
34import static android.provider.DocumentsContract.getRootId;
35import static android.provider.DocumentsContract.getSearchDocumentsQuery;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -070036import static android.provider.DocumentsContract.getTreeDocumentId;
37import static android.provider.DocumentsContract.isTreeUri;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070038
Garfield Tanaba97f32016-10-06 17:34:19 +000039import android.Manifest;
Tor Norbyec615c6f2015-03-02 10:11:44 -080040import android.annotation.CallSuper;
Garfield Tan06940e12016-10-07 16:03:17 -070041import android.annotation.Nullable;
Ben Lindf6d37e2017-03-20 18:51:20 -070042import android.app.AuthenticationRequiredException;
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +090043import android.content.ClipDescription;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070044import android.content.ContentProvider;
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070045import android.content.ContentResolver;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070046import android.content.ContentValues;
47import android.content.Context;
48import android.content.Intent;
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +090049import android.content.IntentSender;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070050import android.content.UriMatcher;
Jeff Sharkeye37ea612013-09-04 14:30:31 -070051import android.content.pm.PackageManager;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070052import android.content.pm.ProviderInfo;
53import android.content.res.AssetFileDescriptor;
54import android.database.Cursor;
55import android.graphics.Point;
56import android.net.Uri;
57import android.os.Bundle;
58import android.os.CancellationSignal;
59import android.os.ParcelFileDescriptor;
Ben Lin8ea82002017-03-08 17:30:16 -080060import android.os.ParcelableException;
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -070061import android.provider.DocumentsContract.Document;
Garfield Tanaba97f32016-10-06 17:34:19 +000062import android.provider.DocumentsContract.Path;
Garfield Tan06940e12016-10-07 16:03:17 -070063import android.provider.DocumentsContract.Root;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070064import android.util.Log;
65
66import libcore.io.IoUtils;
67
68import java.io.FileNotFoundException;
Garfield Tanb690b4d2017-03-01 16:05:23 -080069import java.util.LinkedList;
Jeff Sharkey21de56a2014-04-05 19:05:24 -070070import java.util.Objects;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070071
72/**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070073 * Base class for a document provider. A document provider offers read and write
74 * access to durable files, such as files stored on a local disk, or files in a
75 * cloud storage service. To create a document provider, extend this class,
76 * implement the abstract methods, and add it to your manifest like this:
77 *
78 * <pre class="prettyprint">&lt;manifest&gt;
79 * ...
80 * &lt;application&gt;
81 * ...
82 * &lt;provider
83 * android:name="com.example.MyCloudProvider"
84 * android:authorities="com.example.mycloudprovider"
85 * android:exported="true"
86 * android:grantUriPermissions="true"
Jeff Sharkey3b945402013-11-13 13:31:09 -080087 * android:permission="android.permission.MANAGE_DOCUMENTS"
88 * android:enabled="@bool/isAtLeastKitKat"&gt;
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070089 * &lt;intent-filter&gt;
90 * &lt;action android:name="android.content.action.DOCUMENTS_PROVIDER" /&gt;
91 * &lt;/intent-filter&gt;
92 * &lt;/provider&gt;
93 * ...
94 * &lt;/application&gt;
95 *&lt;/manifest&gt;</pre>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -070096 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -070097 * When defining your provider, you must protect it with
98 * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission
99 * only the system can obtain. Applications cannot use a documents provider
100 * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or
101 * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively
Jeff Sharkey9352c382013-10-23 12:14:34 -0700102 * navigate and select documents. When a user selects documents through that UI,
103 * the system issues narrow URI permission grants to the requesting application.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700104 * </p>
105 * <h3>Documents</h3>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700106 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700107 * A document can be either an openable stream (with a specific MIME type), or a
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700108 * directory containing additional documents (with the
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700109 * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top
110 * of a subtree containing zero or more documents, which can recursively contain
111 * even more documents and directories.
112 * </p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700113 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700114 * Each document can have different capabilities, as described by
115 * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented
Jeff Sharkey9352c382013-10-23 12:14:34 -0700116 * as a thumbnail, your provider can set
117 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700118 * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return
119 * that thumbnail.
120 * </p>
121 * <p>
122 * Each document under a provider is uniquely referenced by its
123 * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A
124 * single document can be included in multiple directories when responding to
125 * {@link #queryChildDocuments(String, String[], String)}. For example, a
126 * provider might surface a single photo in multiple locations: once in a
Jeff Sharkey9352c382013-10-23 12:14:34 -0700127 * directory of geographic locations, and again in a directory of dates.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700128 * </p>
129 * <h3>Roots</h3>
130 * <p>
131 * All documents are surfaced through one or more "roots." Each root represents
132 * the top of a document tree that a user can navigate. For example, a root
133 * could represent an account or a physical storage device. Similar to
134 * documents, each root can have capabilities expressed through
135 * {@link Root#COLUMN_FLAGS}.
136 * </p>
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700137 *
138 * @see Intent#ACTION_OPEN_DOCUMENT
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700139 * @see Intent#ACTION_OPEN_DOCUMENT_TREE
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700140 * @see Intent#ACTION_CREATE_DOCUMENT
141 */
142public abstract class DocumentsProvider extends ContentProvider {
143 private static final String TAG = "DocumentsProvider";
144
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700145 private static final int MATCH_ROOTS = 1;
146 private static final int MATCH_ROOT = 2;
147 private static final int MATCH_RECENT = 3;
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700148 private static final int MATCH_SEARCH = 4;
149 private static final int MATCH_DOCUMENT = 5;
150 private static final int MATCH_CHILDREN = 6;
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700151 private static final int MATCH_DOCUMENT_TREE = 7;
152 private static final int MATCH_CHILDREN_TREE = 8;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700153
154 private String mAuthority;
155
156 private UriMatcher mMatcher;
157
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700158 /**
159 * Implementation is provided by the parent class.
160 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700161 @Override
162 public void attachInfo(Context context, ProviderInfo info) {
Garfield Tan06940e12016-10-07 16:03:17 -0700163 registerAuthority(info.authority);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700164
165 // Sanity check our setup
166 if (!info.exported) {
167 throw new SecurityException("Provider must be exported");
168 }
169 if (!info.grantUriPermissions) {
170 throw new SecurityException("Provider must grantUriPermissions");
171 }
172 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission)
173 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) {
174 throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS");
175 }
176
177 super.attachInfo(context, info);
178 }
179
Garfield Tan06940e12016-10-07 16:03:17 -0700180 /** {@hide} */
181 @Override
182 public void attachInfoForTesting(Context context, ProviderInfo info) {
183 registerAuthority(info.authority);
184
185 super.attachInfoForTesting(context, info);
186 }
187
188 private void registerAuthority(String authority) {
189 mAuthority = authority;
190
191 mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
192 mMatcher.addURI(mAuthority, "root", MATCH_ROOTS);
193 mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT);
194 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT);
195 mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH);
196 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT);
197 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN);
198 mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE);
199 mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE);
200 }
201
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700202 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700203 * Test if a document is descendant (child, grandchild, etc) from the given
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700204 * parent. For example, providers must implement this to support
205 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network
206 * requests to keep this request fast.
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700207 *
208 * @param parentDocumentId parent to verify against.
209 * @param documentId child to verify.
210 * @return if given document is a descendant of the given parent.
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700211 * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700212 */
213 public boolean isChildDocument(String parentDocumentId, String documentId) {
214 return false;
215 }
216
217 /** {@hide} */
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700218 private void enforceTree(Uri documentUri) {
219 if (isTreeUri(documentUri)) {
220 final String parent = getTreeDocumentId(documentUri);
221 final String child = getDocumentId(documentUri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700222 if (Objects.equals(parent, child)) {
223 return;
224 }
225 if (!isChildDocument(parent, child)) {
226 throw new SecurityException(
227 "Document " + child + " is not a descendant of " + parent);
228 }
229 }
230 }
231
232 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700233 * Create a new document and return its newly generated
Jeff Sharkey9352c382013-10-23 12:14:34 -0700234 * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700235 * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must
236 * not change once returned.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700237 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700238 * @param parentDocumentId the parent directory to create the new document
239 * under.
240 * @param mimeType the concrete MIME type associated with the new document.
241 * If the MIME type is not supported, the provider must throw.
242 * @param displayName the display name of the new document. The provider may
243 * alter this name to meet any internal constraints, such as
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700244 * avoiding conflicting names.
Ben Lindf6d37e2017-03-20 18:51:20 -0700245
246 * @throws AuthenticationRequiredException If authentication is required from the user (such as
247 * login credentials), but it is not guaranteed that the client will handle this
248 * properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700249 */
250 @SuppressWarnings("unused")
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700251 public String createDocument(String parentDocumentId, String mimeType, String displayName)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700252 throws FileNotFoundException {
253 throw new UnsupportedOperationException("Create not supported");
254 }
255
256 /**
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700257 * Rename an existing document.
258 * <p>
259 * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to
260 * represent the renamed document, generate and return it. Any outstanding
261 * URI permission grants will be updated to point at the new document. If
262 * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the
263 * rename, return {@code null}.
264 *
265 * @param documentId the document to rename.
266 * @param displayName the updated display name of the document. The provider
267 * may alter this name to meet any internal constraints, such as
268 * avoiding conflicting names.
Ben Lindf6d37e2017-03-20 18:51:20 -0700269 * @throws AuthenticationRequiredException If authentication is required from
270 * the user (such as login credentials), but it is not guaranteed
271 * that the client will handle this properly.
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700272 */
273 @SuppressWarnings("unused")
274 public String renameDocument(String documentId, String displayName)
275 throws FileNotFoundException {
276 throw new UnsupportedOperationException("Rename not supported");
277 }
278
279 /**
280 * Delete the requested document.
281 * <p>
282 * Upon returning, any URI permission grants for the given document will be
283 * revoked. If additional documents were deleted as a side effect of this
284 * call (such as documents inside a directory) the implementor is
285 * responsible for revoking those permissions using
286 * {@link #revokeDocumentPermission(String)}.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700287 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700288 * @param documentId the document to delete.
Ben Lindf6d37e2017-03-20 18:51:20 -0700289 * @throws AuthenticationRequiredException If authentication is required from
290 * the user (such as login credentials), but it is not guaranteed
291 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700292 */
293 @SuppressWarnings("unused")
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700294 public void deleteDocument(String documentId) throws FileNotFoundException {
295 throw new UnsupportedOperationException("Delete not supported");
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700296 }
297
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700298 /**
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700299 * Copy the requested document or a document tree.
300 * <p>
301 * Copies a document including all child documents to another location within
302 * the same document provider. Upon completion returns the document id of
303 * the copied document at the target destination. {@code null} must never
304 * be returned.
305 *
306 * @param sourceDocumentId the document to copy.
307 * @param targetParentDocumentId the target document to be copied into as a child.
Ben Lindf6d37e2017-03-20 18:51:20 -0700308 * @throws AuthenticationRequiredException If authentication is required from
309 * the user (such as login credentials), but it is not guaranteed
310 * that the client will handle this properly.
Tomasz Mikolajewski74fe1812015-06-12 17:13:26 -0700311 */
312 @SuppressWarnings("unused")
313 public String copyDocument(String sourceDocumentId, String targetParentDocumentId)
314 throws FileNotFoundException {
315 throw new UnsupportedOperationException("Copy not supported");
316 }
317
318 /**
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900319 * Move the requested document or a document tree.
Tomasz Mikolajewskieeb8b602016-01-28 13:00:12 +0900320 *
321 * <p>Moves a document including all child documents to another location within
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900322 * the same document provider. Upon completion returns the document id of
323 * the copied document at the target destination. {@code null} must never
324 * be returned.
325 *
Tomasz Mikolajewskieeb8b602016-01-28 13:00:12 +0900326 * <p>It's the responsibility of the provider to revoke grants if the document
327 * is no longer accessible using <code>sourceDocumentId</code>.
328 *
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900329 * @param sourceDocumentId the document to move.
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900330 * @param sourceParentDocumentId the parent of the document to move.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900331 * @param targetParentDocumentId the target document to be a new parent of the
332 * source document.
Ben Lindf6d37e2017-03-20 18:51:20 -0700333 * @throws AuthenticationRequiredException If authentication is required from
334 * the user (such as login credentials), but it is not guaranteed
335 * that the client will handle this properly.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900336 */
337 @SuppressWarnings("unused")
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +0900338 public String moveDocument(String sourceDocumentId, String sourceParentDocumentId,
339 String targetParentDocumentId)
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900340 throws FileNotFoundException {
341 throw new UnsupportedOperationException("Move not supported");
342 }
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900343
344 /**
345 * Removes the requested document or a document tree.
346 *
347 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
348 * This method is especially useful if the document can be in multiple parents.
349 *
350 * <p>It's the responsibility of the provider to revoke grants if the document is
351 * removed from the last parent, and effectively the document is deleted.
352 *
353 * @param documentId the document to remove.
354 * @param parentDocumentId the parent of the document to move.
Ben Lindf6d37e2017-03-20 18:51:20 -0700355 * @throws AuthenticationRequiredException If authentication is required from
356 * the user (such as login credentials), but it is not guaranteed
357 * that the client will handle this properly.
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900358 */
359 @SuppressWarnings("unused")
Tomasz Mikolajewski30714012016-02-15 17:07:37 +0900360 public void removeDocument(String documentId, String parentDocumentId)
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +0900361 throws FileNotFoundException {
362 throw new UnsupportedOperationException("Remove not supported");
363 }
364
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +0900365 /**
Garfield Tan06940e12016-10-07 16:03:17 -0700366 * Finds the canonical path for the requested document. The path must start
367 * from the parent document if parentDocumentId is not null or the root document
368 * if parentDocumentId is null. If there are more than one path to this document,
369 * return the most typical one. Include both the parent document or root document
370 * and the requested document in the returned path.
Garfield Tanaba97f32016-10-06 17:34:19 +0000371 *
Garfield Tan06940e12016-10-07 16:03:17 -0700372 * <p>This API assumes that document ID has enough info to infer the root.
373 * Different roots should use different document ID to refer to the same
Garfield Tanaba97f32016-10-06 17:34:19 +0000374 * document.
Ben Lin8ea82002017-03-08 17:30:16 -0800375 *
Garfield Tanaba97f32016-10-06 17:34:19 +0000376 *
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700377 * @param parentDocumentId the document from which the path starts if not null,
378 * or null to indicate a path from the root is requested.
Garfield Tanb690b4d2017-03-01 16:05:23 -0800379 * @param childDocumentId the document which path is requested.
Garfield Tan06940e12016-10-07 16:03:17 -0700380 * @return the path of the requested document. If parentDocumentId is null
381 * returned root ID must not be null. If parentDocumentId is not null
382 * returned root ID must be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700383 * @throws AuthenticationRequiredException If authentication is required from
384 * the user (such as login credentials), but it is not guaranteed
385 * that the client will handle this properly.
Garfield Tanaba97f32016-10-06 17:34:19 +0000386 */
Garfield Tanb690b4d2017-03-01 16:05:23 -0800387 public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId)
Garfield Tanaba97f32016-10-06 17:34:19 +0000388 throws FileNotFoundException {
Garfield Tan3f6b68a2016-11-01 14:13:38 -0700389 throw new UnsupportedOperationException("findDocumentPath not supported.");
Garfield Tanaba97f32016-10-06 17:34:19 +0000390 }
391
392 /**
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900393 * Creates an intent sender for a web link, if the document is web linkable.
394 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700395 * {@link AuthenticationRequiredException} can be thrown if user does not have
Ben Lin8ea82002017-03-08 17:30:16 -0800396 * sufficient permission for the linked document. Before any new permissions
397 * are granted for the linked document, a visible UI must be shown, so the
398 * user can explicitly confirm whether the permission grants are expected.
399 * The user must be able to cancel the operation.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900400 * <p>
401 * Options passed as an argument may include a list of recipients, such
402 * as email addresses. The provider should reflect these options if possible,
403 * but it's acceptable to ignore them. In either case, confirmation UI must
404 * be shown before any new permission grants are granted.
405 * <p>
406 * It is all right to generate a web link without granting new permissions,
407 * if opening the link would result in a page for requesting permission
408 * access. If it's impossible then the operation must fail by throwing an exception.
409 *
410 * @param documentId the document to create a web link intent for.
411 * @param options additional information, such as list of recipients. Optional.
Ben Lindf6d37e2017-03-20 18:51:20 -0700412 * @throws AuthenticationRequiredException If authentication is required from
413 * the user (such as login credentials), but it is not guaranteed
414 * that the client will handle this properly.
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +0900415 *
416 * @see DocumentsContract.Document#FLAG_WEB_LINKABLE
417 * @see android.app.PendingIntent#getIntentSender
418 */
419 public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options)
420 throws FileNotFoundException {
421 throw new UnsupportedOperationException("createWebLink is not supported.");
422 }
423
424 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700425 * Return all roots currently provided. To display to users, you must define
426 * at least one root. You should avoid making network requests to keep this
427 * request fast.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700428 * <p>
429 * Each root is defined by the metadata columns described in {@link Root},
430 * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory
431 * representing a tree of documents to display under that root.
432 * <p>
433 * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri,
Jeff Sharkey9352c382013-10-23 12:14:34 -0700434 * android.database.ContentObserver, boolean)} with
435 * {@link DocumentsContract#buildRootsUri(String)} to notify the system.
Ben Lin8ea82002017-03-08 17:30:16 -0800436 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700437 *
438 * @param projection list of {@link Root} columns to put into the cursor. If
439 * {@code null} all supported columns should be included.
440 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700441 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException;
442
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700443 /**
444 * Return recently modified documents under the requested root. This will
445 * only be called for roots that advertise
446 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be
447 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and
448 * limited to only return the 64 most recently modified documents.
Jeff Sharkey37ed78e2013-11-05 12:38:21 -0800449 * <p>
450 * Recent documents do not support change notifications.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700451 *
452 * @param projection list of {@link Document} columns to put into the
453 * cursor. If {@code null} all supported columns should be
454 * included.
455 * @see DocumentsContract#EXTRA_LOADING
456 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700457 @SuppressWarnings("unused")
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700458 public Cursor queryRecentDocuments(String rootId, String[] projection)
459 throws FileNotFoundException {
460 throw new UnsupportedOperationException("Recent not supported");
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700461 }
462
463 /**
Jeff Sharkey9352c382013-10-23 12:14:34 -0700464 * Return metadata for the single requested document. You should avoid
465 * making network requests to keep this request fast.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700466 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700467 * @param documentId the document to return.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700468 * @param projection list of {@link Document} columns to put into the
469 * cursor. If {@code null} all supported columns should be
470 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700471 * @throws AuthenticationRequiredException If authentication is required from
472 * the user (such as login credentials), but it is not guaranteed
473 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700474 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700475 public abstract Cursor queryDocument(String documentId, String[] projection)
476 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700477
478 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700479 * Return the children documents contained in the requested directory. This
480 * must only return immediate descendants, as additional queries will be
481 * issued to recursively explore the tree.
482 * <p>
Steve McKay29c3f682016-12-16 14:52:59 -0800483 * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher
484 * should override {@link #queryChildDocuments(String, String[], Bundle)}.
485 * <p>
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700486 * If your provider is cloud-based, and you have some data cached or pinned
487 * locally, you may return the local data immediately, setting
488 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700489 * you are still fetching additional data. Then, when the network data is
490 * available, you can send a change notification to trigger a requery and
Jeff Sharkey3b945402013-11-13 13:31:09 -0800491 * return the complete contents. To return a Cursor with extras, you need to
492 * extend and override {@link Cursor#getExtras()}.
Jeff Sharkey9352c382013-10-23 12:14:34 -0700493 * <p>
494 * To support change notifications, you must
495 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
496 * Uri, such as
497 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
498 * you can call {@link ContentResolver#notifyChange(Uri,
499 * android.database.ContentObserver, boolean)} with that Uri to send change
500 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700501 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700502 * @param parentDocumentId the directory to return children for.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700503 * @param projection list of {@link Document} columns to put into the
504 * cursor. If {@code null} all supported columns should be
505 * included.
506 * @param sortOrder how to order the rows, formatted as an SQL
507 * {@code ORDER BY} clause (excluding the ORDER BY itself).
508 * Passing {@code null} will use the default sort order, which
509 * may be unordered. This ordering is a hint that can be used to
510 * prioritize how data is fetched from the network, but UI may
511 * always enforce a specific ordering.
Ben Lindf6d37e2017-03-20 18:51:20 -0700512 * @throws AuthenticationRequiredException If authentication is required from
513 * the user (such as login credentials), but it is not guaranteed
514 * that the client will handle this properly.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700515 * @see DocumentsContract#EXTRA_LOADING
516 * @see DocumentsContract#EXTRA_INFO
517 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700518 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700519 public abstract Cursor queryChildDocuments(
520 String parentDocumentId, String[] projection, String sortOrder)
521 throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700522
Steve McKay29c3f682016-12-16 14:52:59 -0800523 /**
524 * Override this method to return the children documents contained
525 * in the requested directory. This must return immediate descendants only.
526 *
527 * <p>If your provider is cloud-based, and you have data cached
528 * locally, you may return the local data immediately, setting
529 * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that
530 * you are still fetching additional data. Then, when the network data is
531 * available, you can send a change notification to trigger a requery and
532 * return the complete contents. To return a Cursor with extras, you need to
533 * extend and override {@link Cursor#getExtras()}.
534 *
535 * <p>To support change notifications, you must
536 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
537 * Uri, such as
538 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then
539 * you can call {@link ContentResolver#notifyChange(Uri,
540 * android.database.ContentObserver, boolean)} with that Uri to send change
541 * notifications.
542 *
543 * @param parentDocumentId the directory to return children for.
544 * @param projection list of {@link Document} columns to put into the
545 * cursor. If {@code null} all supported columns should be
546 * included.
547 * @param queryArgs Bundle containing sorting information or other
548 * argument useful to the provider. If no sorting
549 * information is available, default sorting
550 * will be used, which may be unordered. See
551 * {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for
552 * details.
Ben Lindf6d37e2017-03-20 18:51:20 -0700553 * @throws AuthenticationRequiredException If authentication is required from
554 * the user (such as login credentials), but it is not guaranteed
555 * that the client will handle this properly.
Steve McKay29c3f682016-12-16 14:52:59 -0800556 *
557 * @see DocumentsContract#EXTRA_LOADING
558 * @see DocumentsContract#EXTRA_INFO
559 * @see DocumentsContract#EXTRA_ERROR
560 */
561 public Cursor queryChildDocuments(
562 String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)
563 throws FileNotFoundException {
564
565 return queryChildDocuments(
566 parentDocumentId, projection, getSortClause(queryArgs));
567 }
568
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700569 /** {@hide} */
570 @SuppressWarnings("unused")
571 public Cursor queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800572 String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700573 throws FileNotFoundException {
574 throw new UnsupportedOperationException("Manage not supported");
575 }
576
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700577 /**
Mark Dolinerd0646dc2014-08-27 16:04:02 -0700578 * Return documents that match the given query under the requested
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700579 * root. The returned documents should be sorted by relevance in descending
580 * order. How documents are matched against the query string is an
581 * implementation detail left to each provider, but it's suggested that at
582 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a
583 * case-insensitive fashion.
584 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700585 * If your provider is cloud-based, and you have some data cached or pinned
586 * locally, you may return the local data immediately, setting
587 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that
588 * you are still fetching additional data. Then, when the network data is
589 * available, you can send a change notification to trigger a requery and
590 * return the complete contents.
591 * <p>
592 * To support change notifications, you must
593 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant
594 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String,
595 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri,
596 * android.database.ContentObserver, boolean)} with that Uri to send change
597 * notifications.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700598 *
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700599 * @param rootId the root to search under.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700600 * @param query string to match documents against.
601 * @param projection list of {@link Document} columns to put into the
602 * cursor. If {@code null} all supported columns should be
603 * included.
Ben Lindf6d37e2017-03-20 18:51:20 -0700604 * @throws AuthenticationRequiredException If authentication is required from
605 * the user (such as login credentials), but it is not guaranteed
606 * that the client will handle this properly.
607 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700608 * @see DocumentsContract#EXTRA_LOADING
609 * @see DocumentsContract#EXTRA_INFO
610 * @see DocumentsContract#EXTRA_ERROR
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700611 */
612 @SuppressWarnings("unused")
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700613 public Cursor querySearchDocuments(String rootId, String query, String[] projection)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700614 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700615 throw new UnsupportedOperationException("Search not supported");
616 }
617
Garfield Tan87877032017-03-22 12:01:14 -0700618 /**
619 * Ejects the root. Throws {@link IllegalStateException} if ejection failed.
620 *
621 * @param rootId the root to be ejected.
622 * @see Root#FLAG_SUPPORTS_EJECT
623 */
Ben Line7822fb2016-06-24 15:21:08 -0700624 @SuppressWarnings("unused")
Garfield Tan87877032017-03-22 12:01:14 -0700625 public void ejectRoot(String rootId) {
Ben Line7822fb2016-06-24 15:21:08 -0700626 throw new UnsupportedOperationException("Eject not supported");
627 }
628
Julian Mancinib6505152017-06-27 13:29:09 -0700629 /** {@hide} */
Steve McKay17a9ce32017-07-27 13:37:14 -0700630 public @Nullable Bundle getDocumentMetadata(String documentId)
Julian Mancinib6505152017-06-27 13:29:09 -0700631 throws FileNotFoundException {
632 throw new UnsupportedOperationException("Metadata not supported");
633 }
634
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700635 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700636 * Return concrete MIME type of the requested document. Must match the value
637 * of {@link Document#COLUMN_MIME_TYPE} for this document. The default
638 * implementation queries {@link #queryDocument(String, String[])}, so
639 * providers may choose to override this as an optimization.
Ben Lin8ea82002017-03-08 17:30:16 -0800640 * <p>
Ben Lindf6d37e2017-03-20 18:51:20 -0700641 * @throws AuthenticationRequiredException If authentication is required from
642 * the user (such as login credentials), but it is not guaranteed
643 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700644 */
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700645 public String getDocumentType(String documentId) throws FileNotFoundException {
646 final Cursor cursor = queryDocument(documentId, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700647 try {
648 if (cursor.moveToFirst()) {
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700649 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700650 } else {
651 return null;
652 }
653 } finally {
654 IoUtils.closeQuietly(cursor);
655 }
656 }
657
658 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700659 * Open and return the requested document.
660 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700661 * Your provider should return a reliable {@link ParcelFileDescriptor} to
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700662 * detect when the remote caller has finished reading or writing the
Garfield Tanaf03e5a2017-05-15 14:19:11 -0700663 * document.
664 * <p>
665 * Mode "r" should always be supported. Provider should throw
666 * {@link UnsupportedOperationException} if the passing mode is not supported.
667 * You may return a pipe or socket pair if the mode is exclusively "r" or
668 * "w", but complex modes like "rw" imply a normal file on disk that
Jeff Sharkey9352c382013-10-23 12:14:34 -0700669 * supports seeking.
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700670 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700671 * If you block while downloading content, you should periodically check
672 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700673 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700674 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700675 * @param mode the mode to open with, such as 'r', 'w', or 'rw'.
676 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800677 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700678 * @throws AuthenticationRequiredException If authentication is required from
679 * the user (such as login credentials), but it is not guaranteed
680 * that the client will handle this properly.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700681 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler,
682 * OnCloseListener)
683 * @see ParcelFileDescriptor#createReliablePipe()
684 * @see ParcelFileDescriptor#createReliableSocketPair()
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700685 * @see ParcelFileDescriptor#parseMode(String)
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700686 */
687 public abstract ParcelFileDescriptor openDocument(
Steve McKay36f1d7e2017-07-20 11:41:58 -0700688 String documentId,
689 String mode,
690 @Nullable CancellationSignal signal) throws FileNotFoundException;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700691
692 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700693 * Open and return a thumbnail of the requested document.
694 * <p>
695 * A provider should return a thumbnail closely matching the hinted size,
696 * attempting to serve from a local cache if possible. A provider should
697 * never return images more than double the hinted size.
698 * <p>
Jeff Sharkey9352c382013-10-23 12:14:34 -0700699 * If you perform expensive operations to download or generate a thumbnail,
700 * you should periodically check {@link CancellationSignal#isCanceled()} to
701 * abort abandoned thumbnail requests.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700702 *
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700703 * @param documentId the document to return.
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700704 * @param sizeHint hint of the optimal thumbnail dimensions.
705 * @param signal used by the caller to signal if the request should be
Jeff Sharkey3b945402013-11-13 13:31:09 -0800706 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700707 * @throws AuthenticationRequiredException If authentication is required from
708 * the user (such as login credentials), but it is not guaranteed
709 * that the client will handle this properly.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700710 * @see Document#FLAG_SUPPORTS_THUMBNAIL
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700711 */
712 @SuppressWarnings("unused")
713 public AssetFileDescriptor openDocumentThumbnail(
Jeff Sharkeye8c00d82013-10-15 15:46:10 -0700714 String documentId, Point sizeHint, CancellationSignal signal)
715 throws FileNotFoundException {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700716 throw new UnsupportedOperationException("Thumbnails not supported");
717 }
718
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700719 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900720 * Open and return the document in a format matching the specified MIME
721 * type filter.
722 * <p>
723 * A provider may perform a conversion if the documents's MIME type is not
724 * matching the specified MIME type filter.
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +0900725 * <p>
726 * Virtual documents must have at least one streamable format.
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900727 *
728 * @param documentId the document to return.
729 * @param mimeTypeFilter the MIME type filter for the requested format. May
730 * be *\/*, which matches any MIME type.
731 * @param opts extra options from the client. Specific to the content
732 * provider.
733 * @param signal used by the caller to signal if the request should be
734 * cancelled. May be null.
Ben Lindf6d37e2017-03-20 18:51:20 -0700735 * @throws AuthenticationRequiredException If authentication is required from
736 * the user (such as login credentials), but it is not guaranteed
737 * that the client will handle this properly.
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +0900738 * @see #getDocumentStreamTypes(String, String)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900739 */
740 @SuppressWarnings("unused")
741 public AssetFileDescriptor openTypedDocument(
742 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
743 throws FileNotFoundException {
Tomasz Mikolajewski75395652016-01-07 07:19:22 +0000744 throw new FileNotFoundException("The requested MIME type is not supported.");
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900745 }
746
Steve McKay29c3f682016-12-16 14:52:59 -0800747 @Override
748 public final Cursor query(Uri uri, String[] projection, String selection,
749 String[] selectionArgs, String sortOrder) {
750 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
751 // transport method. We override that, and don't ever delegate to this method.
752 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
753 }
754
Steve McKay0bb25292017-01-24 11:45:18 -0800755 /**
756 * WARNING: Sub-classes should not override this method. This method is non-final
757 * solely for the purposes of backwards compatibility.
758 *
759 * @see #queryChildDocuments(String, String[], Bundle),
760 * {@link #queryDocument(String, String[])},
761 * {@link #queryRecentDocuments(String, String[])},
762 * {@link #queryRoots(String[])}, and
763 * {@link #querySearchDocuments(String, String, String[])}.
764 */
Steve McKay29c3f682016-12-16 14:52:59 -0800765 @Override
Steve McKay0bb25292017-01-24 11:45:18 -0800766 public Cursor query(Uri uri, String[] projection, String selection,
Steve McKay29c3f682016-12-16 14:52:59 -0800767 String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
768 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
769 // transport method. We override that, and don't ever delegate to this metohd.
770 throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
771 }
772
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +0900773 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700774 * Implementation is provided by the parent class. Cannot be overriden.
775 *
776 * @see #queryRoots(String[])
777 * @see #queryRecentDocuments(String, String[])
778 * @see #queryDocument(String, String[])
779 * @see #queryChildDocuments(String, String[], String)
780 * @see #querySearchDocuments(String, String, String[])
781 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700782 @Override
Steve McKay29c3f682016-12-16 14:52:59 -0800783 public final Cursor query(
784 Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700785 try {
786 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700787 case MATCH_ROOTS:
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700788 return queryRoots(projection);
789 case MATCH_RECENT:
790 return queryRecentDocuments(getRootId(uri), projection);
Jeff Sharkey3e1189b2013-09-12 21:59:06 -0700791 case MATCH_SEARCH:
792 return querySearchDocuments(
793 getRootId(uri), getSearchDocumentsQuery(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700794 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700795 case MATCH_DOCUMENT_TREE:
796 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700797 return queryDocument(getDocumentId(uri), projection);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700798 case MATCH_CHILDREN:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700799 case MATCH_CHILDREN_TREE:
800 enforceTree(uri);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700801 if (DocumentsContract.isManageMode(uri)) {
Steve McKay29c3f682016-12-16 14:52:59 -0800802 // TODO: Update "ForManage" variant to support query args.
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700803 return queryChildDocumentsForManage(
Steve McKay29c3f682016-12-16 14:52:59 -0800804 getDocumentId(uri),
805 projection,
806 getSortClause(queryArgs));
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700807 } else {
Steve McKay29c3f682016-12-16 14:52:59 -0800808 return queryChildDocuments(getDocumentId(uri), projection, queryArgs);
Jeff Sharkey4ec97392013-09-10 12:04:26 -0700809 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700810 default:
811 throw new UnsupportedOperationException("Unsupported Uri " + uri);
812 }
813 } catch (FileNotFoundException e) {
814 Log.w(TAG, "Failed during query", e);
815 return null;
816 }
817 }
818
Steve McKay29c3f682016-12-16 14:52:59 -0800819 private static @Nullable String getSortClause(@Nullable Bundle queryArgs) {
820 queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY;
821 String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER);
822
823 if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) {
824 sortClause = ContentResolver.createSqlSortClause(queryArgs);
825 }
826
827 return sortClause;
828 }
829
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700830 /**
831 * Implementation is provided by the parent class. Cannot be overriden.
832 *
833 * @see #getDocumentType(String)
834 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700835 @Override
836 public final String getType(Uri uri) {
837 try {
838 switch (mMatcher.match(uri)) {
Jeff Sharkeya61dc8e2013-09-05 17:14:14 -0700839 case MATCH_ROOT:
840 return DocumentsContract.Root.MIME_TYPE_ITEM;
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700841 case MATCH_DOCUMENT:
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700842 case MATCH_DOCUMENT_TREE:
843 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700844 return getDocumentType(getDocumentId(uri));
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700845 default:
846 return null;
847 }
848 } catch (FileNotFoundException e) {
849 Log.w(TAG, "Failed during getType", e);
850 return null;
851 }
852 }
853
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700854 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700855 * Implementation is provided by the parent class. Can be overridden to
856 * provide additional functionality, but subclasses <em>must</em> always
857 * call the superclass. If the superclass returns {@code null}, the subclass
858 * may implement custom behavior.
859 * <p>
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700860 * This is typically used to resolve a subtree URI into a concrete document
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700861 * reference, issuing a narrower single-document URI permission grant along
862 * the way.
863 *
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700864 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String)
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700865 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800866 @CallSuper
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700867 @Override
868 public Uri canonicalize(Uri uri) {
869 final Context context = getContext();
870 switch (mMatcher.match(uri)) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700871 case MATCH_DOCUMENT_TREE:
872 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700873
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -0700874 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri));
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700875
876 // Caller may only have prefix grant, so extend them a grant to
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700877 // the narrow URI.
878 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700879 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags);
880 return narrowUri;
881 }
882 return null;
883 }
884
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700885 private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) {
886 // TODO: move this to a direct AMS call
887 int modeFlags = 0;
888 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
889 == PackageManager.PERMISSION_GRANTED) {
890 modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
891 }
892 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
893 == PackageManager.PERMISSION_GRANTED) {
894 modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
895 }
896 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
897 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
898 == PackageManager.PERMISSION_GRANTED) {
899 modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
900 }
901 return modeFlags;
902 }
903
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700904 /**
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700905 * Implementation is provided by the parent class. Throws by default, and
906 * cannot be overriden.
907 *
908 * @see #createDocument(String, String, String)
909 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700910 @Override
911 public final Uri insert(Uri uri, ContentValues values) {
912 throw new UnsupportedOperationException("Insert not supported");
913 }
914
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700915 /**
916 * Implementation is provided by the parent class. Throws by default, and
917 * cannot be overriden.
918 *
919 * @see #deleteDocument(String)
920 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700921 @Override
922 public final int delete(Uri uri, String selection, String[] selectionArgs) {
923 throw new UnsupportedOperationException("Delete not supported");
924 }
925
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -0700926 /**
927 * Implementation is provided by the parent class. Throws by default, and
928 * cannot be overriden.
929 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700930 @Override
931 public final int update(
932 Uri uri, ContentValues values, String selection, String[] selectionArgs) {
933 throw new UnsupportedOperationException("Update not supported");
934 }
935
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700936 /**
937 * Implementation is provided by the parent class. Can be overridden to
938 * provide additional functionality, but subclasses <em>must</em> always
939 * call the superclass. If the superclass returns {@code null}, the subclass
940 * may implement custom behavior.
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700941 */
Tor Norbyec615c6f2015-03-02 10:11:44 -0800942 @CallSuper
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700943 @Override
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700944 public Bundle call(String method, String arg, Bundle extras) {
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700945 if (!method.startsWith("android:")) {
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700946 // Ignore non-platform methods
Jeff Sharkey911d7f42013-09-05 18:11:45 -0700947 return super.call(method, arg, extras);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700948 }
949
Steve McKayd3afdee2015-11-19 17:27:12 -0800950 try {
951 return callUnchecked(method, arg, extras);
952 } catch (FileNotFoundException e) {
Ben Lin8ea82002017-03-08 17:30:16 -0800953 throw new ParcelableException(e);
Steve McKayd3afdee2015-11-19 17:27:12 -0800954 }
955 }
956
957 private Bundle callUnchecked(String method, String arg, Bundle extras)
958 throws FileNotFoundException {
959
Jeff Sharkeyb7e12552014-05-21 22:22:03 -0700960 final Context context = getContext();
Ben Lind7d14872016-06-28 17:12:52 -0700961 final Bundle out = new Bundle();
962
963 if (METHOD_EJECT_ROOT.equals(method)) {
964 // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps
965 // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for
Garfield Tan87877032017-03-22 12:01:14 -0700966 // MANAGE_DOCUMENTS or associated URI permission here instead
Ben Lind7d14872016-06-28 17:12:52 -0700967 final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
Garfield Tan87877032017-03-22 12:01:14 -0700968 enforceWritePermissionInner(rootUri, getCallingPackage(), null);
Ben Lind7d14872016-06-28 17:12:52 -0700969
Garfield Tan87877032017-03-22 12:01:14 -0700970 final String rootId = DocumentsContract.getRootId(rootUri);
971 ejectRoot(rootId);
Ben Lind7d14872016-06-28 17:12:52 -0700972
973 return out;
974 }
975
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700976 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI);
977 final String authority = documentUri.getAuthority();
978 final String documentId = DocumentsContract.getDocumentId(documentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -0700979
Jeff Sharkey21de56a2014-04-05 19:05:24 -0700980 if (!mAuthority.equals(authority)) {
981 throw new SecurityException(
982 "Requested authority " + authority + " doesn't match provider " + mAuthority);
983 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700984
Steve McKayd3afdee2015-11-19 17:27:12 -0800985 // If the URI is a tree URI performs some validation.
986 enforceTree(documentUri);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -0700987
Steve McKayd3afdee2015-11-19 17:27:12 -0800988 if (METHOD_IS_CHILD_DOCUMENT.equals(method)) {
989 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
990
991 final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
992 final String childAuthority = childUri.getAuthority();
993 final String childId = DocumentsContract.getDocumentId(childUri);
994
995 out.putBoolean(
996 DocumentsContract.EXTRA_RESULT,
997 mAuthority.equals(childAuthority)
998 && isChildDocument(documentId, childId));
999
1000 } else if (METHOD_CREATE_DOCUMENT.equals(method)) {
1001 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1002
1003 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE);
1004 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1005 final String newDocumentId = createDocument(documentId, mimeType, displayName);
1006
1007 // No need to issue new grants here, since caller either has
1008 // manage permission or a prefix grant. We might generate a
1009 // tree style URI if that's how they called us.
1010 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1011 newDocumentId);
1012 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1013
Tomasz Mikolajewskicf316562016-10-24 15:17:01 +09001014 } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) {
1015 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1016
1017 final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS);
1018 final IntentSender intentSender = createWebLinkIntent(documentId, options);
1019
1020 out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender);
1021
Steve McKayd3afdee2015-11-19 17:27:12 -08001022 } else if (METHOD_RENAME_DOCUMENT.equals(method)) {
1023 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1024
1025 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME);
1026 final String newDocumentId = renameDocument(documentId, displayName);
1027
1028 if (newDocumentId != null) {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001029 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001030 newDocumentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001031
1032 // If caller came in with a narrow grant, issue them a
1033 // narrow grant for the newly renamed document.
1034 if (!isTreeUri(newDocumentUri)) {
1035 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1036 documentUri);
1037 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1038 }
1039
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001040 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
Jeff Sharkeye37ea612013-09-04 14:30:31 -07001041
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001042 // Original document no longer exists, clean up any grants.
Tomasz Mikolajewskia375a992015-06-25 15:39:27 +09001043 revokeDocumentPermission(documentId);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001044 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001045
1046 } else if (METHOD_DELETE_DOCUMENT.equals(method)) {
1047 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1048 deleteDocument(documentId);
1049
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001050 // Document no longer exists, clean up any grants.
Steve McKayd3afdee2015-11-19 17:27:12 -08001051 revokeDocumentPermission(documentId);
1052
1053 } else if (METHOD_COPY_DOCUMENT.equals(method)) {
1054 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1055 final String targetId = DocumentsContract.getDocumentId(targetUri);
1056
1057 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1058 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1059
1060 final String newDocumentId = copyDocument(documentId, targetId);
1061
1062 if (newDocumentId != null) {
1063 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1064 newDocumentId);
1065
1066 if (!isTreeUri(newDocumentUri)) {
1067 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1068 documentUri);
1069 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1070 }
1071
1072 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1073 }
1074
1075 } else if (METHOD_MOVE_DOCUMENT.equals(method)) {
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001076 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1077 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
Steve McKayd3afdee2015-11-19 17:27:12 -08001078 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI);
1079 final String targetId = DocumentsContract.getDocumentId(targetUri);
1080
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001081 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1082 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
Steve McKayd3afdee2015-11-19 17:27:12 -08001083 enforceWritePermissionInner(targetUri, getCallingPackage(), null);
1084
Tomasz Mikolajewskid46ecbc2016-01-25 14:26:54 +09001085 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001086
1087 if (newDocumentId != null) {
1088 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri,
1089 newDocumentId);
1090
1091 if (!isTreeUri(newDocumentUri)) {
1092 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context,
1093 documentUri);
1094 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags);
1095 }
1096
1097 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri);
1098 }
1099
Tomasz Mikolajewskicbcd3942016-01-28 12:39:25 +09001100 } else if (METHOD_REMOVE_DOCUMENT.equals(method)) {
1101 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI);
1102 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri);
1103
1104 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null);
1105 enforceWritePermissionInner(documentUri, getCallingPackage(), null);
1106 removeDocument(documentId, parentSourceId);
1107
1108 // It's responsibility of the provider to revoke any grants, as the document may be
1109 // still attached to another parents.
Garfield Tan3f6b68a2016-11-01 14:13:38 -07001110 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) {
Garfield Tan06940e12016-10-07 16:03:17 -07001111 final boolean isTreeUri = isTreeUri(documentUri);
Garfield Tanaba97f32016-10-06 17:34:19 +00001112
Garfield Tan06940e12016-10-07 16:03:17 -07001113 if (isTreeUri) {
1114 enforceReadPermissionInner(documentUri, getCallingPackage(), null);
1115 } else {
1116 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null);
1117 }
1118
1119 final String parentDocumentId = isTreeUri
1120 ? DocumentsContract.getTreeDocumentId(documentUri)
1121 : null;
1122
Garfield Tanb690b4d2017-03-01 16:05:23 -08001123 Path path = findDocumentPath(parentDocumentId, documentId);
Garfield Tan06940e12016-10-07 16:03:17 -07001124
1125 // Ensure provider doesn't leak information to unprivileged callers.
Garfield Tan5f214802016-10-26 14:52:46 -07001126 if (isTreeUri) {
1127 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) {
1128 Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: "
1129 + parentDocumentId + " found: " + path.getPath().get(0));
Garfield Tanb690b4d2017-03-01 16:05:23 -08001130
1131 LinkedList<String> docs = new LinkedList<>(path.getPath());
1132 while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) {
1133 docs.removeFirst();
1134 }
1135 path = new Path(null, docs);
Garfield Tan5f214802016-10-26 14:52:46 -07001136 }
1137
1138 if (path.getRootId() != null) {
1139 Log.wtf(TAG, "Provider returns root id :"
1140 + path.getRootId() + " unexpectedly. Erase root id.");
1141 path = new Path(null, path.getPath());
1142 }
Garfield Tan06940e12016-10-07 16:03:17 -07001143 }
Garfield Tanaba97f32016-10-06 17:34:19 +00001144
1145 out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
Julian Mancinib6505152017-06-27 13:29:09 -07001146 } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
Steve McKay17a9ce32017-07-27 13:37:14 -07001147 return getDocumentMetadata(documentId);
Steve McKayd3afdee2015-11-19 17:27:12 -08001148 } else {
1149 throw new UnsupportedOperationException("Method not supported " + method);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001150 }
Steve McKayd3afdee2015-11-19 17:27:12 -08001151
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001152 return out;
1153 }
1154
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001155 /**
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001156 * Revoke any active permission grants for the given
1157 * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document
1158 * becomes invalid. Follows the same semantics as
1159 * {@link Context#revokeUriPermission(Uri, int)}.
1160 */
1161 public final void revokeDocumentPermission(String documentId) {
1162 final Context context = getContext();
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001163 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0);
1164 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001165 }
1166
1167 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001168 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001169 *
1170 * @see #openDocument(String, String, CancellationSignal)
1171 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001172 @Override
1173 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001174 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001175 return openDocument(getDocumentId(uri), mode, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001176 }
1177
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001178 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001179 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001180 *
1181 * @see #openDocument(String, String, CancellationSignal)
1182 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001183 @Override
1184 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal)
1185 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001186 enforceTree(uri);
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001187 return openDocument(getDocumentId(uri), mode, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001188 }
1189
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001190 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001191 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001192 *
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001193 * @see #openDocument(String, String, CancellationSignal)
1194 */
1195 @Override
1196 @SuppressWarnings("resource")
1197 public final AssetFileDescriptor openAssetFile(Uri uri, String mode)
1198 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001199 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001200 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null);
1201 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1202 }
1203
1204 /**
1205 * Implementation is provided by the parent class. Cannot be overriden.
1206 *
1207 * @see #openDocument(String, String, CancellationSignal)
1208 */
1209 @Override
1210 @SuppressWarnings("resource")
1211 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal)
1212 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001213 enforceTree(uri);
Jeff Sharkey21de56a2014-04-05 19:05:24 -07001214 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal);
1215 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null;
1216 }
1217
1218 /**
1219 * Implementation is provided by the parent class. Cannot be overriden.
1220 *
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001221 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001222 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001223 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001224 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001225 @Override
1226 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)
1227 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001228 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001229 }
1230
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001231 /**
Jeff Sharkeye8c00d82013-10-15 15:46:10 -07001232 * Implementation is provided by the parent class. Cannot be overriden.
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001233 *
1234 * @see #openDocumentThumbnail(String, Point, CancellationSignal)
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001235 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001236 * @see #getDocumentStreamTypes(String, String)
Jeff Sharkeyae9b51b2013-08-31 15:02:20 -07001237 */
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001238 @Override
1239 public final AssetFileDescriptor openTypedAssetFile(
1240 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1241 throws FileNotFoundException {
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001242 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal);
1243 }
1244
1245 /**
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001246 * Return a list of streamable MIME types matching the filter, which can be passed to
1247 * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}.
1248 *
1249 * <p>The default implementation returns a MIME type provided by
1250 * {@link #queryDocument(String, String[])} as long as it matches the filter and the document
1251 * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set.
1252 *
Tomasz Mikolajewski099f9512016-12-09 10:19:46 +09001253 * <p>Virtual documents must have at least one streamable format.
1254 *
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001255 * @see #getStreamTypes(Uri, String)
1256 * @see #openTypedDocument(String, String, Bundle, CancellationSignal)
1257 */
1258 public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) {
1259 Cursor cursor = null;
1260 try {
1261 cursor = queryDocument(documentId, null);
1262 if (cursor.moveToFirst()) {
1263 final String mimeType =
1264 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE));
1265 final long flags =
1266 cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS));
1267 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null &&
1268 mimeTypeMatches(mimeTypeFilter, mimeType)) {
1269 return new String[] { mimeType };
1270 }
1271 }
1272 } catch (FileNotFoundException e) {
1273 return null;
1274 } finally {
1275 IoUtils.closeQuietly(cursor);
1276 }
1277
1278 // No streamable MIME types.
1279 return null;
1280 }
1281
1282 /**
1283 * Called by a client to determine the types of data streams that this content provider
1284 * support for the given URI.
1285 *
1286 * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead.
1287 *
1288 * @see #getDocumentStreamTypes(String, String)
1289 */
1290 @Override
1291 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
1292 enforceTree(uri);
1293 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter);
1294 }
1295
1296 /**
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001297 * @hide
1298 */
1299 private final AssetFileDescriptor openTypedAssetFileImpl(
1300 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)
1301 throws FileNotFoundException {
Jeff Sharkeyb9fbb722014-06-04 16:42:47 -07001302 enforceTree(uri);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001303 final String documentId = getDocumentId(uri);
Jeff Sharkey5b836f22014-08-27 14:46:32 -07001304 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) {
1305 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE);
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001306 return openDocumentThumbnail(documentId, sizeHint, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001307 }
Tomasz Mikolajewskib344b892015-10-28 17:50:40 +09001308 if ("*/*".equals(mimeTypeFilter)) {
1309 // If they can take anything, the untyped open call is good enough.
1310 return openAssetFile(uri, "r");
1311 }
1312 final String baseType = getType(uri);
1313 if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) {
1314 // Use old untyped open call if this provider has a type for this
1315 // URI and it matches the request.
1316 return openAssetFile(uri, "r");
1317 }
1318 // For any other yet unhandled case, let the provider subclass handle it.
1319 return openTypedDocument(documentId, mimeTypeFilter, opts, signal);
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001320 }
Tomasz Mikolajewskid99964f2016-02-15 11:16:32 +09001321
1322 /**
1323 * @hide
1324 */
1325 public static boolean mimeTypeMatches(String filter, String test) {
1326 if (test == null) {
1327 return false;
1328 } else if (filter == null || "*/*".equals(filter)) {
1329 return true;
1330 } else if (filter.equals(test)) {
1331 return true;
1332 } else if (filter.endsWith("/*")) {
1333 return filter.regionMatches(0, test, 0, filter.indexOf('/'));
1334 } else {
1335 return false;
1336 }
1337 }
Jeff Sharkeyaeb16e22013-08-27 18:26:48 -07001338}