1 /* 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 17 package android.provider; 18 19 import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT; 20 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; 21 import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT; 22 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; 23 import static android.provider.DocumentsContract.METHOD_EJECT_ROOT; 24 import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH; 25 import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA; 26 import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT; 27 import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT; 28 import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT; 29 import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; 30 import static android.provider.DocumentsContract.buildDocumentUri; 31 import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; 32 import static android.provider.DocumentsContract.buildTreeDocumentUri; 33 import static android.provider.DocumentsContract.getDocumentId; 34 import static android.provider.DocumentsContract.getRootId; 35 import static android.provider.DocumentsContract.getTreeDocumentId; 36 import static android.provider.DocumentsContract.isTreeUri; 37 38 import android.Manifest; 39 import android.annotation.CallSuper; 40 import android.annotation.NonNull; 41 import android.annotation.Nullable; 42 import android.app.AuthenticationRequiredException; 43 import android.content.ClipDescription; 44 import android.content.ContentProvider; 45 import android.content.ContentResolver; 46 import android.content.ContentValues; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.IntentSender; 50 import android.content.MimeTypeFilter; 51 import android.content.UriMatcher; 52 import android.content.pm.PackageManager; 53 import android.content.pm.ProviderInfo; 54 import android.content.res.AssetFileDescriptor; 55 import android.database.Cursor; 56 import android.graphics.Point; 57 import android.net.Uri; 58 import android.os.Bundle; 59 import android.os.CancellationSignal; 60 import android.os.ParcelFileDescriptor; 61 import android.os.ParcelableException; 62 import android.provider.DocumentsContract.Document; 63 import android.provider.DocumentsContract.Path; 64 import android.provider.DocumentsContract.Root; 65 import android.util.Log; 66 67 import com.android.internal.util.Preconditions; 68 69 import libcore.io.IoUtils; 70 71 import java.io.FileNotFoundException; 72 import java.util.LinkedList; 73 import java.util.Objects; 74 75 /** 76 * 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"><manifest> 82 * ... 83 * <application> 84 * ... 85 * <provider 86 * android:name="com.example.MyCloudProvider" 87 * android:authorities="com.example.mycloudprovider" 88 * android:exported="true" 89 * android:grantUriPermissions="true" 90 * android:permission="android.permission.MANAGE_DOCUMENTS" 91 * android:enabled="@bool/isAtLeastKitKat"> 92 * <intent-filter> 93 * <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 94 * </intent-filter> 95 * </provider> 96 * ... 97 * </application> 98 *</manifest></pre> 99 * <p> 100 * 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 105 * navigate and select documents. When a user selects documents through that UI, 106 * the system issues narrow URI permission grants to the requesting application. 107 * </p> 108 * <h3>Documents</h3> 109 * <p> 110 * A document can be either an openable stream (with a specific MIME type), or a 111 * directory containing additional documents (with the 112 * {@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> 116 * <p> 117 * Each document can have different capabilities, as described by 118 * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented 119 * as a thumbnail, your provider can set 120 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement 121 * {@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 130 * directory of geographic locations, and again in a directory of dates. 131 * </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> 140 * 141 * @see Intent#ACTION_OPEN_DOCUMENT 142 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 143 * @see Intent#ACTION_CREATE_DOCUMENT 144 */ 145 public abstract class DocumentsProvider extends ContentProvider { 146 private static final String TAG = "DocumentsProvider"; 147 148 private static final int MATCH_ROOTS = 1; 149 private static final int MATCH_ROOT = 2; 150 private static final int MATCH_RECENT = 3; 151 private static final int MATCH_SEARCH = 4; 152 private static final int MATCH_DOCUMENT = 5; 153 private static final int MATCH_CHILDREN = 6; 154 private static final int MATCH_DOCUMENT_TREE = 7; 155 private static final int MATCH_CHILDREN_TREE = 8; 156 157 private String mAuthority; 158 159 private UriMatcher mMatcher; 160 161 /** 162 * Implementation is provided by the parent class. 163 */ 164 @Override attachInfo(Context context, ProviderInfo info)165 public void attachInfo(Context context, ProviderInfo info) { 166 registerAuthority(info.authority); 167 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 183 /** {@hide} */ 184 @Override attachInfoForTesting(Context context, ProviderInfo info)185 public void attachInfoForTesting(Context context, ProviderInfo info) { 186 registerAuthority(info.authority); 187 188 super.attachInfoForTesting(context, info); 189 } 190 registerAuthority(String authority)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 205 /** 206 * Test if a document is descendant (child, grandchild, etc) from the given 207 * 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. 210 * 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. 214 * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD 215 */ isChildDocument(String parentDocumentId, String documentId)216 public boolean isChildDocument(String parentDocumentId, String documentId) { 217 return false; 218 } 219 220 /** {@hide} */ enforceTree(Uri documentUri)221 private void enforceTree(Uri documentUri) { 222 if (isTreeUri(documentUri)) { 223 final String parent = getTreeDocumentId(documentUri); 224 final String child = getDocumentId(documentUri); 225 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 /** 236 * Create a new document and return its newly generated 237 * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new 238 * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must 239 * not change once returned. 240 * 241 * @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 247 * avoiding conflicting names. 248 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. 252 */ 253 @SuppressWarnings("unused") createDocument(String parentDocumentId, String mimeType, String displayName)254 public String createDocument(String parentDocumentId, String mimeType, String displayName) 255 throws FileNotFoundException { 256 throw new UnsupportedOperationException("Create not supported"); 257 } 258 259 /** 260 * 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. 272 * @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. 275 */ 276 @SuppressWarnings("unused") renameDocument(String documentId, String displayName)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)}. 290 * 291 * @param documentId the document to delete. 292 * @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. 295 */ 296 @SuppressWarnings("unused") deleteDocument(String documentId)297 public void deleteDocument(String documentId) throws FileNotFoundException { 298 throw new UnsupportedOperationException("Delete not supported"); 299 } 300 301 /** 302 * 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. 311 * @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. 314 */ 315 @SuppressWarnings("unused") copyDocument(String sourceDocumentId, String targetParentDocumentId)316 public String copyDocument(String sourceDocumentId, String targetParentDocumentId) 317 throws FileNotFoundException { 318 throw new UnsupportedOperationException("Copy not supported"); 319 } 320 321 /** 322 * Move the requested document or a document tree. 323 * 324 * <p>Moves a document including all child documents to another location within 325 * 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 * 329 * <p>It's the responsibility of the provider to revoke grants if the document 330 * is no longer accessible using <code>sourceDocumentId</code>. 331 * 332 * @param sourceDocumentId the document to move. 333 * @param sourceParentDocumentId the parent of the document to move. 334 * @param targetParentDocumentId the target document to be a new parent of the 335 * source document. 336 * @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. 339 */ 340 @SuppressWarnings("unused") moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)341 public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, 342 String targetParentDocumentId) 343 throws FileNotFoundException { 344 throw new UnsupportedOperationException("Move not supported"); 345 } 346 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. 358 * @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. 361 */ 362 @SuppressWarnings("unused") removeDocument(String documentId, String parentDocumentId)363 public void removeDocument(String documentId, String parentDocumentId) 364 throws FileNotFoundException { 365 throw new UnsupportedOperationException("Remove not supported"); 366 } 367 368 /** 369 * 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. 374 * 375 * <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 377 * document. 378 * 379 * 380 * @param parentDocumentId the document from which the path starts if not null, 381 * or null to indicate a path from the root is requested. 382 * @param childDocumentId the document which path is requested. 383 * @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. 386 * @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. 389 */ findDocumentPath(@ullable String parentDocumentId, String childDocumentId)390 public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) 391 throws FileNotFoundException { 392 throw new UnsupportedOperationException("findDocumentPath not supported."); 393 } 394 395 /** 396 * Creates an intent sender for a web link, if the document is web linkable. 397 * <p> 398 * {@link AuthenticationRequiredException} can be thrown if user does not have 399 * 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. 403 * <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. 415 * @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. 418 * 419 * @see DocumentsContract.Document#FLAG_WEB_LINKABLE 420 * @see android.app.PendingIntent#getIntentSender 421 */ createWebLinkIntent(String documentId, @Nullable Bundle options)422 public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options) 423 throws FileNotFoundException { 424 throw new UnsupportedOperationException("createWebLink is not supported."); 425 } 426 427 /** 428 * 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. 431 * <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, 437 * android.database.ContentObserver, boolean)} with 438 * {@link DocumentsContract#buildRootsUri(String)} to notify the system. 439 * <p> 440 * 441 * @param projection list of {@link Root} columns to put into the cursor. If 442 * {@code null} all supported columns should be included. 443 */ queryRoots(String[] projection)444 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; 445 446 /** 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. 452 * <p> 453 * Recent documents do not support change notifications. 454 * 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 */ 460 @SuppressWarnings("unused") queryRecentDocuments(String rootId, String[] projection)461 public Cursor queryRecentDocuments(String rootId, String[] projection) 462 throws FileNotFoundException { 463 throw new UnsupportedOperationException("Recent not supported"); 464 } 465 466 /** 467 * 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 474 * {@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}. 480 * <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") 492 @Nullable queryRecentDocuments( @onNull String rootId, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal signal)493 public Cursor queryRecentDocuments( 494 @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 498 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 /** 506 * Return metadata for the single requested document. You should avoid 507 * making network requests to keep this request fast. 508 * 509 * @param documentId the document to return. 510 * @param projection list of {@link Document} columns to put into the 511 * cursor. If {@code null} all supported columns should be 512 * included. 513 * @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. 516 */ queryDocument(String documentId, String[] projection)517 public abstract Cursor queryDocument(String documentId, String[] projection) 518 throws FileNotFoundException; 519 520 /** 521 * 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> 525 * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher 526 * should override {@link #queryChildDocuments(String, String[], Bundle)}. 527 * <p> 528 * 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 531 * 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 533 * return the complete contents. To return a Cursor with extras, you need to 534 * extend and override {@link Cursor#getExtras()}. 535 * <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. 543 * 544 * @param parentDocumentId the directory to return children for. 545 * @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. 554 * @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. 557 * @see DocumentsContract#EXTRA_LOADING 558 * @see DocumentsContract#EXTRA_INFO 559 * @see DocumentsContract#EXTRA_ERROR 560 */ queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)561 public abstract Cursor queryChildDocuments( 562 String parentDocumentId, String[] projection, String sortOrder) 563 throws FileNotFoundException; 564 565 /** 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. 595 * @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. 598 * 599 * @see DocumentsContract#EXTRA_LOADING 600 * @see DocumentsContract#EXTRA_INFO 601 * @see DocumentsContract#EXTRA_ERROR 602 */ queryChildDocuments( String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)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 611 /** {@hide} */ 612 @SuppressWarnings("unused") queryChildDocumentsForManage( String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)613 public Cursor queryChildDocumentsForManage( 614 String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder) 615 throws FileNotFoundException { 616 throw new UnsupportedOperationException("Manage not supported"); 617 } 618 619 /** 620 * Return documents that match the given query under the requested 621 * 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> 627 * 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. 640 * 641 * @param rootId the root to search under. 642 * @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. 646 * @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 * 650 * @see DocumentsContract#EXTRA_LOADING 651 * @see DocumentsContract#EXTRA_INFO 652 * @see DocumentsContract#EXTRA_ERROR 653 */ 654 @SuppressWarnings("unused") querySearchDocuments(String rootId, String query, String[] projection)655 public Cursor querySearchDocuments(String rootId, String query, String[] projection) 656 throws FileNotFoundException { 657 throw new UnsupportedOperationException("Search not supported"); 658 } 659 660 /** 661 * 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. 687 * {@link DocumentsContract#QUERY_ARG_EXCLUDE_MEDIA}, 688 * {@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 * 697 * @see Root#COLUMN_QUERY_ARGS 698 * @see ContentResolver#EXTRA_HONORED_ARGS 699 * @see DocumentsContract#EXTRA_LOADING 700 * @see DocumentsContract#EXTRA_INFO 701 * @see DocumentsContract#EXTRA_ERROR 702 */ 703 @SuppressWarnings("unused") 704 @Nullable querySearchDocuments(@onNull String rootId, @Nullable String[] projection, @NonNull Bundle queryArgs)705 public Cursor querySearchDocuments(@NonNull String rootId, 706 @Nullable String[] projection, @NonNull Bundle queryArgs) throws FileNotFoundException { 707 Preconditions.checkNotNull(rootId, "rootId can not be null"); 708 Preconditions.checkNotNull(queryArgs, "queryArgs can not be null"); 709 return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs), 710 projection); 711 } 712 713 /** 714 * 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 */ 719 @SuppressWarnings("unused") ejectRoot(String rootId)720 public void ejectRoot(String rootId) { 721 throw new UnsupportedOperationException("Eject not supported"); 722 } 723 724 /** 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 */ getDocumentMetadata(@onNull String documentId)746 public @Nullable Bundle getDocumentMetadata(@NonNull String documentId) 747 throws FileNotFoundException { 748 throw new UnsupportedOperationException("Metadata not supported"); 749 } 750 751 /** 752 * 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. 756 * <p> 757 * @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. 760 */ getDocumentType(String documentId)761 public String getDocumentType(String documentId) throws FileNotFoundException { 762 final Cursor cursor = queryDocument(documentId, null); 763 try { 764 if (cursor.moveToFirst()) { 765 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 766 } else { 767 return null; 768 } 769 } finally { 770 IoUtils.closeQuietly(cursor); 771 } 772 } 773 774 /** 775 * Open and return the requested document. 776 * <p> 777 * Your provider should return a reliable {@link ParcelFileDescriptor} to 778 * detect when the remote caller has finished reading or writing the 779 * 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 785 * supports seeking. 786 * <p> 787 * If you block while downloading content, you should periodically check 788 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests. 789 * 790 * @param documentId the document to return. 791 * @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 793 * cancelled. May be null. 794 * @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. 797 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, 798 * OnCloseListener) 799 * @see ParcelFileDescriptor#createReliablePipe() 800 * @see ParcelFileDescriptor#createReliableSocketPair() 801 * @see ParcelFileDescriptor#parseMode(String) 802 */ openDocument( String documentId, String mode, @Nullable CancellationSignal signal)803 public abstract ParcelFileDescriptor openDocument( 804 String documentId, 805 String mode, 806 @Nullable CancellationSignal signal) throws FileNotFoundException; 807 808 /** 809 * 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> 815 * 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. 818 * 819 * @param documentId the document to return. 820 * @param sizeHint hint of the optimal thumbnail dimensions. 821 * @param signal used by the caller to signal if the request should be 822 * cancelled. May be null. 823 * @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. 826 * @see Document#FLAG_SUPPORTS_THUMBNAIL 827 */ 828 @SuppressWarnings("unused") openDocumentThumbnail( String documentId, Point sizeHint, CancellationSignal signal)829 public AssetFileDescriptor openDocumentThumbnail( 830 String documentId, Point sizeHint, CancellationSignal signal) 831 throws FileNotFoundException { 832 throw new UnsupportedOperationException("Thumbnails not supported"); 833 } 834 835 /** 836 * 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. 841 * <p> 842 * Virtual documents must have at least one streamable format. 843 * 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. 851 * @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. 854 * @see #getDocumentStreamTypes(String, String) 855 */ 856 @SuppressWarnings("unused") openTypedDocument( String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)857 public AssetFileDescriptor openTypedDocument( 858 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 859 throws FileNotFoundException { 860 throw new FileNotFoundException("The requested MIME type is not supported."); 861 } 862 863 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)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 871 /** 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 879 * {@link #querySearchDocuments(String, String[], Bundle)}. 880 */ 881 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)882 public Cursor query(Uri uri, String[] projection, String selection, 883 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 889 /** 890 * Implementation is provided by the parent class. Cannot be overridden. 891 * 892 * @see #queryRoots(String[]) 893 * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal) 894 * @see #queryDocument(String, String[]) 895 * @see #queryChildDocuments(String, String[], String) 896 * @see #querySearchDocuments(String, String[], Bundle) 897 */ 898 @Override query( Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal)899 public final Cursor query( 900 Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) { 901 try { 902 switch (mMatcher.match(uri)) { 903 case MATCH_ROOTS: 904 return queryRoots(projection); 905 case MATCH_RECENT: 906 return queryRecentDocuments( 907 getRootId(uri), projection, queryArgs, cancellationSignal); 908 case MATCH_SEARCH: 909 return querySearchDocuments(getRootId(uri), projection, queryArgs); 910 case MATCH_DOCUMENT: 911 case MATCH_DOCUMENT_TREE: 912 enforceTree(uri); 913 return queryDocument(getDocumentId(uri), projection); 914 case MATCH_CHILDREN: 915 case MATCH_CHILDREN_TREE: 916 enforceTree(uri); 917 if (DocumentsContract.isManageMode(uri)) { 918 // TODO: Update "ForManage" variant to support query args. 919 return queryChildDocumentsForManage( 920 getDocumentId(uri), 921 projection, 922 getSortClause(queryArgs)); 923 } else { 924 return queryChildDocuments(getDocumentId(uri), projection, queryArgs); 925 } 926 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 getSortClause(@ullable Bundle queryArgs)935 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 946 /** 947 * Implementation is provided by the parent class. Cannot be overridden. 948 * 949 * @see #getDocumentType(String) 950 */ 951 @Override getType(Uri uri)952 public final String getType(Uri uri) { 953 try { 954 switch (mMatcher.match(uri)) { 955 case MATCH_ROOT: 956 return DocumentsContract.Root.MIME_TYPE_ITEM; 957 case MATCH_DOCUMENT: 958 case MATCH_DOCUMENT_TREE: 959 enforceTree(uri); 960 return getDocumentType(getDocumentId(uri)); 961 default: 962 return null; 963 } 964 } catch (FileNotFoundException e) { 965 Log.w(TAG, "Failed during getType", e); 966 return null; 967 } 968 } 969 970 /** 971 * 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> 976 * This is typically used to resolve a subtree URI into a concrete document 977 * reference, issuing a narrower single-document URI permission grant along 978 * the way. 979 * 980 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String) 981 */ 982 @CallSuper 983 @Override canonicalize(Uri uri)984 public Uri canonicalize(Uri uri) { 985 final Context context = getContext(); 986 switch (mMatcher.match(uri)) { 987 case MATCH_DOCUMENT_TREE: 988 enforceTree(uri); 989 990 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri)); 991 992 // Caller may only have prefix grant, so extend them a grant to 993 // the narrow URI. 994 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri); 995 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); 996 return narrowUri; 997 } 998 return null; 999 } 1000 getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri)1001 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 1020 /** 1021 * Implementation is provided by the parent class. Throws by default, and 1022 * cannot be overridden. 1023 * 1024 * @see #createDocument(String, String, String) 1025 */ 1026 @Override insert(Uri uri, ContentValues values)1027 public final Uri insert(Uri uri, ContentValues values) { 1028 throw new UnsupportedOperationException("Insert not supported"); 1029 } 1030 1031 /** 1032 * Implementation is provided by the parent class. Throws by default, and 1033 * cannot be overridden. 1034 * 1035 * @see #deleteDocument(String) 1036 */ 1037 @Override delete(Uri uri, String selection, String[] selectionArgs)1038 public final int delete(Uri uri, String selection, String[] selectionArgs) { 1039 throw new UnsupportedOperationException("Delete not supported"); 1040 } 1041 1042 /** 1043 * Implementation is provided by the parent class. Throws by default, and 1044 * cannot be overridden. 1045 */ 1046 @Override update( Uri uri, ContentValues values, String selection, String[] selectionArgs)1047 public final int update( 1048 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1049 throw new UnsupportedOperationException("Update not supported"); 1050 } 1051 1052 /** 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. 1057 */ 1058 @CallSuper 1059 @Override call(String method, String arg, Bundle extras)1060 public Bundle call(String method, String arg, Bundle extras) { 1061 if (!method.startsWith("android:")) { 1062 // Ignore non-platform methods 1063 return super.call(method, arg, extras); 1064 } 1065 1066 try { 1067 return callUnchecked(method, arg, extras); 1068 } catch (FileNotFoundException e) { 1069 throw new ParcelableException(e); 1070 } 1071 } 1072 callUnchecked(String method, String arg, Bundle extras)1073 private Bundle callUnchecked(String method, String arg, Bundle extras) 1074 throws FileNotFoundException { 1075 1076 final Context context = getContext(); 1077 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 1082 // MANAGE_DOCUMENTS or associated URI permission here instead 1083 final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI); 1084 enforceWritePermissionInner(rootUri, getCallingPackage(), null); 1085 1086 final String rootId = DocumentsContract.getRootId(rootUri); 1087 ejectRoot(rootId); 1088 1089 return out; 1090 } 1091 1092 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); 1093 final String authority = documentUri.getAuthority(); 1094 final String documentId = DocumentsContract.getDocumentId(documentUri); 1095 1096 if (!mAuthority.equals(authority)) { 1097 throw new SecurityException( 1098 "Requested authority " + authority + " doesn't match provider " + mAuthority); 1099 } 1100 1101 // If the URI is a tree URI performs some validation. 1102 enforceTree(documentUri); 1103 1104 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 1130 } 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 1138 } 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) { 1145 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 1146 newDocumentId); 1147 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 1156 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 1157 1158 // Original document no longer exists, clean up any grants. 1159 revokeDocumentPermission(documentId); 1160 } 1161 1162 } else if (METHOD_DELETE_DOCUMENT.equals(method)) { 1163 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 1164 deleteDocument(documentId); 1165 1166 // Document no longer exists, clean up any grants. 1167 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)) { 1192 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI); 1193 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); 1194 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI); 1195 final String targetId = DocumentsContract.getDocumentId(targetUri); 1196 1197 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 1198 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null); 1199 enforceWritePermissionInner(targetUri, getCallingPackage(), null); 1200 1201 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId); 1202 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 1216 } 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. 1226 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) { 1227 final boolean isTreeUri = isTreeUri(documentUri); 1228 1229 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 1239 Path path = findDocumentPath(parentDocumentId, documentId); 1240 1241 // Ensure provider doesn't leak information to unprivileged callers. 1242 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)); 1246 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); 1252 } 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 } 1259 } 1260 1261 out.putParcelable(DocumentsContract.EXTRA_RESULT, path); 1262 } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) { 1263 return getDocumentMetadata(documentId); 1264 } else { 1265 throw new UnsupportedOperationException("Method not supported " + method); 1266 } 1267 1268 return out; 1269 } 1270 1271 /** 1272 * 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 */ revokeDocumentPermission(String documentId)1277 public final void revokeDocumentPermission(String documentId) { 1278 final Context context = getContext(); 1279 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0); 1280 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0); 1281 } 1282 1283 /** 1284 * Implementation is provided by the parent class. Cannot be overridden. 1285 * 1286 * @see #openDocument(String, String, CancellationSignal) 1287 */ 1288 @Override openFile(Uri uri, String mode)1289 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 1290 enforceTree(uri); 1291 return openDocument(getDocumentId(uri), mode, null); 1292 } 1293 1294 /** 1295 * Implementation is provided by the parent class. Cannot be overridden. 1296 * 1297 * @see #openDocument(String, String, CancellationSignal) 1298 */ 1299 @Override openFile(Uri uri, String mode, CancellationSignal signal)1300 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) 1301 throws FileNotFoundException { 1302 enforceTree(uri); 1303 return openDocument(getDocumentId(uri), mode, signal); 1304 } 1305 1306 /** 1307 * Implementation is provided by the parent class. Cannot be overridden. 1308 * 1309 * @see #openDocument(String, String, CancellationSignal) 1310 */ 1311 @Override 1312 @SuppressWarnings("resource") openAssetFile(Uri uri, String mode)1313 public final AssetFileDescriptor openAssetFile(Uri uri, String mode) 1314 throws FileNotFoundException { 1315 enforceTree(uri); 1316 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null); 1317 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 1318 } 1319 1320 /** 1321 * Implementation is provided by the parent class. Cannot be overridden. 1322 * 1323 * @see #openDocument(String, String, CancellationSignal) 1324 */ 1325 @Override 1326 @SuppressWarnings("resource") openAssetFile(Uri uri, String mode, CancellationSignal signal)1327 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal) 1328 throws FileNotFoundException { 1329 enforceTree(uri); 1330 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal); 1331 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 1332 } 1333 1334 /** 1335 * Implementation is provided by the parent class. Cannot be overridden. 1336 * 1337 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 1338 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1339 * @see #getDocumentStreamTypes(String, String) 1340 */ 1341 @Override openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)1342 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) 1343 throws FileNotFoundException { 1344 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null); 1345 } 1346 1347 /** 1348 * Implementation is provided by the parent class. Cannot be overridden. 1349 * 1350 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 1351 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1352 * @see #getDocumentStreamTypes(String, String) 1353 */ 1354 @Override openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)1355 public final AssetFileDescriptor openTypedAssetFile( 1356 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 1357 throws FileNotFoundException { 1358 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal); 1359 } 1360 1361 /** 1362 * 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 * 1369 * <p>Virtual documents must have at least one streamable format. 1370 * 1371 * @see #getStreamTypes(Uri, String) 1372 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1373 */ getDocumentStreamTypes(String documentId, String mimeTypeFilter)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 && 1384 MimeTypeFilter.matches(mimeType, mimeTypeFilter)) { 1385 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 getStreamTypes(Uri uri, String mimeTypeFilter)1407 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { 1408 enforceTree(uri); 1409 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter); 1410 } 1411 1412 /** 1413 * @hide 1414 */ openTypedAssetFileImpl( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)1415 private final AssetFileDescriptor openTypedAssetFileImpl( 1416 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 1417 throws FileNotFoundException { 1418 enforceTree(uri); 1419 final String documentId = getDocumentId(uri); 1420 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) { 1421 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE); 1422 return openDocumentThumbnail(documentId, sizeHint, signal); 1423 } 1424 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); 1436 } 1437 } 1438