DocumentsProvider.java revision 099f95181d5fdf905d4a77c9a364c458030064b1
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 17package android.provider; 18 19import static android.provider.DocumentsContract.METHOD_COPY_DOCUMENT; 20import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; 21import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; 22import static android.provider.DocumentsContract.METHOD_EJECT_ROOT; 23import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH; 24import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT; 25import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT; 26import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT; 27import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; 28import static android.provider.DocumentsContract.buildDocumentUri; 29import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; 30import static android.provider.DocumentsContract.buildTreeDocumentUri; 31import static android.provider.DocumentsContract.getDocumentId; 32import static android.provider.DocumentsContract.getRootId; 33import static android.provider.DocumentsContract.getSearchDocumentsQuery; 34import static android.provider.DocumentsContract.getTreeDocumentId; 35import static android.provider.DocumentsContract.isTreeUri; 36 37import android.Manifest; 38import android.annotation.CallSuper; 39import android.annotation.Nullable; 40import android.content.ClipDescription; 41import android.content.ContentProvider; 42import android.content.ContentResolver; 43import android.content.ContentValues; 44import android.content.Context; 45import android.content.Intent; 46import android.content.UriMatcher; 47import android.content.pm.PackageManager; 48import android.content.pm.ProviderInfo; 49import android.content.res.AssetFileDescriptor; 50import android.database.Cursor; 51import android.graphics.Point; 52import android.net.Uri; 53import android.os.Bundle; 54import android.os.CancellationSignal; 55import android.os.ParcelFileDescriptor; 56import android.os.ParcelFileDescriptor.OnCloseListener; 57import android.provider.DocumentsContract.Document; 58import android.provider.DocumentsContract.Path; 59import android.provider.DocumentsContract.Root; 60import android.util.Log; 61 62import libcore.io.IoUtils; 63 64import java.io.FileNotFoundException; 65import java.util.Objects; 66 67/** 68 * Base class for a document provider. A document provider offers read and write 69 * access to durable files, such as files stored on a local disk, or files in a 70 * cloud storage service. To create a document provider, extend this class, 71 * implement the abstract methods, and add it to your manifest like this: 72 * 73 * <pre class="prettyprint"><manifest> 74 * ... 75 * <application> 76 * ... 77 * <provider 78 * android:name="com.example.MyCloudProvider" 79 * android:authorities="com.example.mycloudprovider" 80 * android:exported="true" 81 * android:grantUriPermissions="true" 82 * android:permission="android.permission.MANAGE_DOCUMENTS" 83 * android:enabled="@bool/isAtLeastKitKat"> 84 * <intent-filter> 85 * <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 86 * </intent-filter> 87 * </provider> 88 * ... 89 * </application> 90 *</manifest></pre> 91 * <p> 92 * When defining your provider, you must protect it with 93 * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission 94 * only the system can obtain. Applications cannot use a documents provider 95 * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or 96 * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively 97 * navigate and select documents. When a user selects documents through that UI, 98 * the system issues narrow URI permission grants to the requesting application. 99 * </p> 100 * <h3>Documents</h3> 101 * <p> 102 * A document can be either an openable stream (with a specific MIME type), or a 103 * directory containing additional documents (with the 104 * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top 105 * of a subtree containing zero or more documents, which can recursively contain 106 * even more documents and directories. 107 * </p> 108 * <p> 109 * Each document can have different capabilities, as described by 110 * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented 111 * as a thumbnail, your provider can set 112 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement 113 * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return 114 * that thumbnail. 115 * </p> 116 * <p> 117 * Each document under a provider is uniquely referenced by its 118 * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A 119 * single document can be included in multiple directories when responding to 120 * {@link #queryChildDocuments(String, String[], String)}. For example, a 121 * provider might surface a single photo in multiple locations: once in a 122 * directory of geographic locations, and again in a directory of dates. 123 * </p> 124 * <h3>Roots</h3> 125 * <p> 126 * All documents are surfaced through one or more "roots." Each root represents 127 * the top of a document tree that a user can navigate. For example, a root 128 * could represent an account or a physical storage device. Similar to 129 * documents, each root can have capabilities expressed through 130 * {@link Root#COLUMN_FLAGS}. 131 * </p> 132 * 133 * @see Intent#ACTION_OPEN_DOCUMENT 134 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 135 * @see Intent#ACTION_CREATE_DOCUMENT 136 */ 137public abstract class DocumentsProvider extends ContentProvider { 138 private static final String TAG = "DocumentsProvider"; 139 140 private static final int MATCH_ROOTS = 1; 141 private static final int MATCH_ROOT = 2; 142 private static final int MATCH_RECENT = 3; 143 private static final int MATCH_SEARCH = 4; 144 private static final int MATCH_DOCUMENT = 5; 145 private static final int MATCH_CHILDREN = 6; 146 private static final int MATCH_DOCUMENT_TREE = 7; 147 private static final int MATCH_CHILDREN_TREE = 8; 148 149 private String mAuthority; 150 151 private UriMatcher mMatcher; 152 153 /** 154 * Implementation is provided by the parent class. 155 */ 156 @Override 157 public void attachInfo(Context context, ProviderInfo info) { 158 registerAuthority(info.authority); 159 160 // Sanity check our setup 161 if (!info.exported) { 162 throw new SecurityException("Provider must be exported"); 163 } 164 if (!info.grantUriPermissions) { 165 throw new SecurityException("Provider must grantUriPermissions"); 166 } 167 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission) 168 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) { 169 throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS"); 170 } 171 172 super.attachInfo(context, info); 173 } 174 175 /** {@hide} */ 176 @Override 177 public void attachInfoForTesting(Context context, ProviderInfo info) { 178 registerAuthority(info.authority); 179 180 super.attachInfoForTesting(context, info); 181 } 182 183 private void registerAuthority(String authority) { 184 mAuthority = authority; 185 186 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); 187 mMatcher.addURI(mAuthority, "root", MATCH_ROOTS); 188 mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT); 189 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); 190 mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH); 191 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); 192 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); 193 mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE); 194 mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE); 195 } 196 197 /** 198 * Test if a document is descendant (child, grandchild, etc) from the given 199 * parent. For example, providers must implement this to support 200 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network 201 * requests to keep this request fast. 202 * 203 * @param parentDocumentId parent to verify against. 204 * @param documentId child to verify. 205 * @return if given document is a descendant of the given parent. 206 * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD 207 */ 208 public boolean isChildDocument(String parentDocumentId, String documentId) { 209 return false; 210 } 211 212 /** {@hide} */ 213 private void enforceTree(Uri documentUri) { 214 if (isTreeUri(documentUri)) { 215 final String parent = getTreeDocumentId(documentUri); 216 final String child = getDocumentId(documentUri); 217 if (Objects.equals(parent, child)) { 218 return; 219 } 220 if (!isChildDocument(parent, child)) { 221 throw new SecurityException( 222 "Document " + child + " is not a descendant of " + parent); 223 } 224 } 225 } 226 227 /** 228 * Create a new document and return its newly generated 229 * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new 230 * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must 231 * not change once returned. 232 * 233 * @param parentDocumentId the parent directory to create the new document 234 * under. 235 * @param mimeType the concrete MIME type associated with the new document. 236 * If the MIME type is not supported, the provider must throw. 237 * @param displayName the display name of the new document. The provider may 238 * alter this name to meet any internal constraints, such as 239 * avoiding conflicting names. 240 */ 241 @SuppressWarnings("unused") 242 public String createDocument(String parentDocumentId, String mimeType, String displayName) 243 throws FileNotFoundException { 244 throw new UnsupportedOperationException("Create not supported"); 245 } 246 247 /** 248 * Rename an existing document. 249 * <p> 250 * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to 251 * represent the renamed document, generate and return it. Any outstanding 252 * URI permission grants will be updated to point at the new document. If 253 * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the 254 * rename, return {@code null}. 255 * 256 * @param documentId the document to rename. 257 * @param displayName the updated display name of the document. The provider 258 * may alter this name to meet any internal constraints, such as 259 * avoiding conflicting names. 260 */ 261 @SuppressWarnings("unused") 262 public String renameDocument(String documentId, String displayName) 263 throws FileNotFoundException { 264 throw new UnsupportedOperationException("Rename not supported"); 265 } 266 267 /** 268 * Delete the requested document. 269 * <p> 270 * Upon returning, any URI permission grants for the given document will be 271 * revoked. If additional documents were deleted as a side effect of this 272 * call (such as documents inside a directory) the implementor is 273 * responsible for revoking those permissions using 274 * {@link #revokeDocumentPermission(String)}. 275 * 276 * @param documentId the document to delete. 277 */ 278 @SuppressWarnings("unused") 279 public void deleteDocument(String documentId) throws FileNotFoundException { 280 throw new UnsupportedOperationException("Delete not supported"); 281 } 282 283 /** 284 * Copy the requested document or a document tree. 285 * <p> 286 * Copies a document including all child documents to another location within 287 * the same document provider. Upon completion returns the document id of 288 * the copied document at the target destination. {@code null} must never 289 * be returned. 290 * 291 * @param sourceDocumentId the document to copy. 292 * @param targetParentDocumentId the target document to be copied into as a child. 293 */ 294 @SuppressWarnings("unused") 295 public String copyDocument(String sourceDocumentId, String targetParentDocumentId) 296 throws FileNotFoundException { 297 throw new UnsupportedOperationException("Copy not supported"); 298 } 299 300 /** 301 * Move the requested document or a document tree. 302 * 303 * <p>Moves a document including all child documents to another location within 304 * the same document provider. Upon completion returns the document id of 305 * the copied document at the target destination. {@code null} must never 306 * be returned. 307 * 308 * <p>It's the responsibility of the provider to revoke grants if the document 309 * is no longer accessible using <code>sourceDocumentId</code>. 310 * 311 * @param sourceDocumentId the document to move. 312 * @param sourceParentDocumentId the parent of the document to move. 313 * @param targetParentDocumentId the target document to be a new parent of the 314 * source document. 315 */ 316 @SuppressWarnings("unused") 317 public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, 318 String targetParentDocumentId) 319 throws FileNotFoundException { 320 throw new UnsupportedOperationException("Move not supported"); 321 } 322 323 /** 324 * Removes the requested document or a document tree. 325 * 326 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent. 327 * This method is especially useful if the document can be in multiple parents. 328 * 329 * <p>It's the responsibility of the provider to revoke grants if the document is 330 * removed from the last parent, and effectively the document is deleted. 331 * 332 * @param documentId the document to remove. 333 * @param parentDocumentId the parent of the document to move. 334 */ 335 @SuppressWarnings("unused") 336 public void removeDocument(String documentId, String parentDocumentId) 337 throws FileNotFoundException { 338 throw new UnsupportedOperationException("Remove not supported"); 339 } 340 341 /** 342 * Finds the canonical path for the requested document. The path must start 343 * from the parent document if parentDocumentId is not null or the root document 344 * if parentDocumentId is null. If there are more than one path to this document, 345 * return the most typical one. Include both the parent document or root document 346 * and the requested document in the returned path. 347 * 348 * <p>This API assumes that document ID has enough info to infer the root. 349 * Different roots should use different document ID to refer to the same 350 * document. 351 * 352 * @param childDocumentId the document which path is requested. 353 * @param parentDocumentId the document from which the path starts if not null, 354 * or null to indicate a path from the root is requested. 355 * @return the path of the requested document. If parentDocumentId is null 356 * returned root ID must not be null. If parentDocumentId is not null 357 * returned root ID must be null. 358 */ 359 public Path findDocumentPath(String childDocumentId, @Nullable String parentDocumentId) 360 throws FileNotFoundException { 361 throw new UnsupportedOperationException("findDocumentPath not supported."); 362 } 363 364 /** 365 * Return all roots currently provided. To display to users, you must define 366 * at least one root. You should avoid making network requests to keep this 367 * request fast. 368 * <p> 369 * Each root is defined by the metadata columns described in {@link Root}, 370 * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory 371 * representing a tree of documents to display under that root. 372 * <p> 373 * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri, 374 * android.database.ContentObserver, boolean)} with 375 * {@link DocumentsContract#buildRootsUri(String)} to notify the system. 376 * 377 * @param projection list of {@link Root} columns to put into the cursor. If 378 * {@code null} all supported columns should be included. 379 */ 380 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; 381 382 /** 383 * Return recently modified documents under the requested root. This will 384 * only be called for roots that advertise 385 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be 386 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and 387 * limited to only return the 64 most recently modified documents. 388 * <p> 389 * Recent documents do not support change notifications. 390 * 391 * @param projection list of {@link Document} columns to put into the 392 * cursor. If {@code null} all supported columns should be 393 * included. 394 * @see DocumentsContract#EXTRA_LOADING 395 */ 396 @SuppressWarnings("unused") 397 public Cursor queryRecentDocuments(String rootId, String[] projection) 398 throws FileNotFoundException { 399 throw new UnsupportedOperationException("Recent not supported"); 400 } 401 402 /** 403 * Return metadata for the single requested document. You should avoid 404 * making network requests to keep this request fast. 405 * 406 * @param documentId the document to return. 407 * @param projection list of {@link Document} columns to put into the 408 * cursor. If {@code null} all supported columns should be 409 * included. 410 */ 411 public abstract Cursor queryDocument(String documentId, String[] projection) 412 throws FileNotFoundException; 413 414 /** 415 * Return the children documents contained in the requested directory. This 416 * must only return immediate descendants, as additional queries will be 417 * issued to recursively explore the tree. 418 * <p> 419 * If your provider is cloud-based, and you have some data cached or pinned 420 * locally, you may return the local data immediately, setting 421 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that 422 * you are still fetching additional data. Then, when the network data is 423 * available, you can send a change notification to trigger a requery and 424 * return the complete contents. To return a Cursor with extras, you need to 425 * extend and override {@link Cursor#getExtras()}. 426 * <p> 427 * To support change notifications, you must 428 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant 429 * Uri, such as 430 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then 431 * you can call {@link ContentResolver#notifyChange(Uri, 432 * android.database.ContentObserver, boolean)} with that Uri to send change 433 * notifications. 434 * 435 * @param parentDocumentId the directory to return children for. 436 * @param projection list of {@link Document} columns to put into the 437 * cursor. If {@code null} all supported columns should be 438 * included. 439 * @param sortOrder how to order the rows, formatted as an SQL 440 * {@code ORDER BY} clause (excluding the ORDER BY itself). 441 * Passing {@code null} will use the default sort order, which 442 * may be unordered. This ordering is a hint that can be used to 443 * prioritize how data is fetched from the network, but UI may 444 * always enforce a specific ordering. 445 * @see DocumentsContract#EXTRA_LOADING 446 * @see DocumentsContract#EXTRA_INFO 447 * @see DocumentsContract#EXTRA_ERROR 448 */ 449 public abstract Cursor queryChildDocuments( 450 String parentDocumentId, String[] projection, String sortOrder) 451 throws FileNotFoundException; 452 453 /** {@hide} */ 454 @SuppressWarnings("unused") 455 public Cursor queryChildDocumentsForManage( 456 String parentDocumentId, String[] projection, String sortOrder) 457 throws FileNotFoundException { 458 throw new UnsupportedOperationException("Manage not supported"); 459 } 460 461 /** 462 * Return documents that match the given query under the requested 463 * root. The returned documents should be sorted by relevance in descending 464 * order. How documents are matched against the query string is an 465 * implementation detail left to each provider, but it's suggested that at 466 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a 467 * case-insensitive fashion. 468 * <p> 469 * If your provider is cloud-based, and you have some data cached or pinned 470 * locally, you may return the local data immediately, setting 471 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that 472 * you are still fetching additional data. Then, when the network data is 473 * available, you can send a change notification to trigger a requery and 474 * return the complete contents. 475 * <p> 476 * To support change notifications, you must 477 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant 478 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String, 479 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri, 480 * android.database.ContentObserver, boolean)} with that Uri to send change 481 * notifications. 482 * 483 * @param rootId the root to search under. 484 * @param query string to match documents against. 485 * @param projection list of {@link Document} columns to put into the 486 * cursor. If {@code null} all supported columns should be 487 * included. 488 * @see DocumentsContract#EXTRA_LOADING 489 * @see DocumentsContract#EXTRA_INFO 490 * @see DocumentsContract#EXTRA_ERROR 491 */ 492 @SuppressWarnings("unused") 493 public Cursor querySearchDocuments(String rootId, String query, String[] projection) 494 throws FileNotFoundException { 495 throw new UnsupportedOperationException("Search not supported"); 496 } 497 498 /** {@hide} */ 499 @SuppressWarnings("unused") 500 public boolean ejectRoot(String rootId) { 501 throw new UnsupportedOperationException("Eject not supported"); 502 } 503 504 /** 505 * Return concrete MIME type of the requested document. Must match the value 506 * of {@link Document#COLUMN_MIME_TYPE} for this document. The default 507 * implementation queries {@link #queryDocument(String, String[])}, so 508 * providers may choose to override this as an optimization. 509 */ 510 public String getDocumentType(String documentId) throws FileNotFoundException { 511 final Cursor cursor = queryDocument(documentId, null); 512 try { 513 if (cursor.moveToFirst()) { 514 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 515 } else { 516 return null; 517 } 518 } finally { 519 IoUtils.closeQuietly(cursor); 520 } 521 } 522 523 /** 524 * Open and return the requested document. 525 * <p> 526 * Your provider should return a reliable {@link ParcelFileDescriptor} to 527 * detect when the remote caller has finished reading or writing the 528 * document. You may return a pipe or socket pair if the mode is exclusively 529 * "r" or "w", but complex modes like "rw" imply a normal file on disk that 530 * supports seeking. 531 * <p> 532 * If you block while downloading content, you should periodically check 533 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests. 534 * 535 * @param documentId the document to return. 536 * @param mode the mode to open with, such as 'r', 'w', or 'rw'. 537 * @param signal used by the caller to signal if the request should be 538 * cancelled. May be null. 539 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, 540 * OnCloseListener) 541 * @see ParcelFileDescriptor#createReliablePipe() 542 * @see ParcelFileDescriptor#createReliableSocketPair() 543 * @see ParcelFileDescriptor#parseMode(String) 544 */ 545 public abstract ParcelFileDescriptor openDocument( 546 String documentId, String mode, CancellationSignal signal) throws FileNotFoundException; 547 548 /** 549 * Open and return a thumbnail of the requested document. 550 * <p> 551 * A provider should return a thumbnail closely matching the hinted size, 552 * attempting to serve from a local cache if possible. A provider should 553 * never return images more than double the hinted size. 554 * <p> 555 * If you perform expensive operations to download or generate a thumbnail, 556 * you should periodically check {@link CancellationSignal#isCanceled()} to 557 * abort abandoned thumbnail requests. 558 * 559 * @param documentId the document to return. 560 * @param sizeHint hint of the optimal thumbnail dimensions. 561 * @param signal used by the caller to signal if the request should be 562 * cancelled. May be null. 563 * @see Document#FLAG_SUPPORTS_THUMBNAIL 564 */ 565 @SuppressWarnings("unused") 566 public AssetFileDescriptor openDocumentThumbnail( 567 String documentId, Point sizeHint, CancellationSignal signal) 568 throws FileNotFoundException { 569 throw new UnsupportedOperationException("Thumbnails not supported"); 570 } 571 572 /** 573 * Open and return the document in a format matching the specified MIME 574 * type filter. 575 * <p> 576 * A provider may perform a conversion if the documents's MIME type is not 577 * matching the specified MIME type filter. 578 * <p> 579 * Virtual documents must have at least one streamable format. 580 * 581 * @param documentId the document to return. 582 * @param mimeTypeFilter the MIME type filter for the requested format. May 583 * be *\/*, which matches any MIME type. 584 * @param opts extra options from the client. Specific to the content 585 * provider. 586 * @param signal used by the caller to signal if the request should be 587 * cancelled. May be null. 588 * @see #getDocumentStreamTypes(String, String) 589 */ 590 @SuppressWarnings("unused") 591 public AssetFileDescriptor openTypedDocument( 592 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 593 throws FileNotFoundException { 594 throw new FileNotFoundException("The requested MIME type is not supported."); 595 } 596 597 /** 598 * Implementation is provided by the parent class. Cannot be overriden. 599 * 600 * @see #queryRoots(String[]) 601 * @see #queryRecentDocuments(String, String[]) 602 * @see #queryDocument(String, String[]) 603 * @see #queryChildDocuments(String, String[], String) 604 * @see #querySearchDocuments(String, String, String[]) 605 */ 606 @Override 607 public final Cursor query(Uri uri, String[] projection, String selection, 608 String[] selectionArgs, String sortOrder) { 609 try { 610 switch (mMatcher.match(uri)) { 611 case MATCH_ROOTS: 612 return queryRoots(projection); 613 case MATCH_RECENT: 614 return queryRecentDocuments(getRootId(uri), projection); 615 case MATCH_SEARCH: 616 return querySearchDocuments( 617 getRootId(uri), getSearchDocumentsQuery(uri), projection); 618 case MATCH_DOCUMENT: 619 case MATCH_DOCUMENT_TREE: 620 enforceTree(uri); 621 return queryDocument(getDocumentId(uri), projection); 622 case MATCH_CHILDREN: 623 case MATCH_CHILDREN_TREE: 624 enforceTree(uri); 625 if (DocumentsContract.isManageMode(uri)) { 626 return queryChildDocumentsForManage( 627 getDocumentId(uri), projection, sortOrder); 628 } else { 629 return queryChildDocuments(getDocumentId(uri), projection, sortOrder); 630 } 631 default: 632 throw new UnsupportedOperationException("Unsupported Uri " + uri); 633 } 634 } catch (FileNotFoundException e) { 635 Log.w(TAG, "Failed during query", e); 636 return null; 637 } 638 } 639 640 /** 641 * Implementation is provided by the parent class. Cannot be overriden. 642 * 643 * @see #getDocumentType(String) 644 */ 645 @Override 646 public final String getType(Uri uri) { 647 try { 648 switch (mMatcher.match(uri)) { 649 case MATCH_ROOT: 650 return DocumentsContract.Root.MIME_TYPE_ITEM; 651 case MATCH_DOCUMENT: 652 case MATCH_DOCUMENT_TREE: 653 enforceTree(uri); 654 return getDocumentType(getDocumentId(uri)); 655 default: 656 return null; 657 } 658 } catch (FileNotFoundException e) { 659 Log.w(TAG, "Failed during getType", e); 660 return null; 661 } 662 } 663 664 /** 665 * Implementation is provided by the parent class. Can be overridden to 666 * provide additional functionality, but subclasses <em>must</em> always 667 * call the superclass. If the superclass returns {@code null}, the subclass 668 * may implement custom behavior. 669 * <p> 670 * This is typically used to resolve a subtree URI into a concrete document 671 * reference, issuing a narrower single-document URI permission grant along 672 * the way. 673 * 674 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String) 675 */ 676 @CallSuper 677 @Override 678 public Uri canonicalize(Uri uri) { 679 final Context context = getContext(); 680 switch (mMatcher.match(uri)) { 681 case MATCH_DOCUMENT_TREE: 682 enforceTree(uri); 683 684 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri)); 685 686 // Caller may only have prefix grant, so extend them a grant to 687 // the narrow URI. 688 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri); 689 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); 690 return narrowUri; 691 } 692 return null; 693 } 694 695 private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) { 696 // TODO: move this to a direct AMS call 697 int modeFlags = 0; 698 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) 699 == PackageManager.PERMISSION_GRANTED) { 700 modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION; 701 } 702 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) 703 == PackageManager.PERMISSION_GRANTED) { 704 modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; 705 } 706 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION 707 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) 708 == PackageManager.PERMISSION_GRANTED) { 709 modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; 710 } 711 return modeFlags; 712 } 713 714 /** 715 * Implementation is provided by the parent class. Throws by default, and 716 * cannot be overriden. 717 * 718 * @see #createDocument(String, String, String) 719 */ 720 @Override 721 public final Uri insert(Uri uri, ContentValues values) { 722 throw new UnsupportedOperationException("Insert not supported"); 723 } 724 725 /** 726 * Implementation is provided by the parent class. Throws by default, and 727 * cannot be overriden. 728 * 729 * @see #deleteDocument(String) 730 */ 731 @Override 732 public final int delete(Uri uri, String selection, String[] selectionArgs) { 733 throw new UnsupportedOperationException("Delete not supported"); 734 } 735 736 /** 737 * Implementation is provided by the parent class. Throws by default, and 738 * cannot be overriden. 739 */ 740 @Override 741 public final int update( 742 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 743 throw new UnsupportedOperationException("Update not supported"); 744 } 745 746 /** 747 * Implementation is provided by the parent class. Can be overridden to 748 * provide additional functionality, but subclasses <em>must</em> always 749 * call the superclass. If the superclass returns {@code null}, the subclass 750 * may implement custom behavior. 751 */ 752 @CallSuper 753 @Override 754 public Bundle call(String method, String arg, Bundle extras) { 755 if (!method.startsWith("android:")) { 756 // Ignore non-platform methods 757 return super.call(method, arg, extras); 758 } 759 760 try { 761 return callUnchecked(method, arg, extras); 762 } catch (FileNotFoundException e) { 763 throw new IllegalStateException("Failed call " + method, e); 764 } 765 } 766 767 private Bundle callUnchecked(String method, String arg, Bundle extras) 768 throws FileNotFoundException { 769 770 final Context context = getContext(); 771 final Bundle out = new Bundle(); 772 773 if (METHOD_EJECT_ROOT.equals(method)) { 774 // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps 775 // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for 776 // MANAGE_DOCUMENTS here instead 777 getContext().enforceCallingPermission( 778 android.Manifest.permission.MANAGE_DOCUMENTS, null); 779 final Uri rootUri = extras.getParcelable(DocumentsContract.EXTRA_URI); 780 final String rootId = DocumentsContract.getRootId(rootUri); 781 final boolean ejected = ejectRoot(rootId); 782 783 out.putBoolean(DocumentsContract.EXTRA_RESULT, ejected); 784 785 return out; 786 } 787 788 final Uri documentUri = extras.getParcelable(DocumentsContract.EXTRA_URI); 789 final String authority = documentUri.getAuthority(); 790 final String documentId = DocumentsContract.getDocumentId(documentUri); 791 792 if (!mAuthority.equals(authority)) { 793 throw new SecurityException( 794 "Requested authority " + authority + " doesn't match provider " + mAuthority); 795 } 796 797 // If the URI is a tree URI performs some validation. 798 enforceTree(documentUri); 799 800 if (METHOD_IS_CHILD_DOCUMENT.equals(method)) { 801 enforceReadPermissionInner(documentUri, getCallingPackage(), null); 802 803 final Uri childUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI); 804 final String childAuthority = childUri.getAuthority(); 805 final String childId = DocumentsContract.getDocumentId(childUri); 806 807 out.putBoolean( 808 DocumentsContract.EXTRA_RESULT, 809 mAuthority.equals(childAuthority) 810 && isChildDocument(documentId, childId)); 811 812 } else if (METHOD_CREATE_DOCUMENT.equals(method)) { 813 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 814 815 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); 816 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 817 final String newDocumentId = createDocument(documentId, mimeType, displayName); 818 819 // No need to issue new grants here, since caller either has 820 // manage permission or a prefix grant. We might generate a 821 // tree style URI if that's how they called us. 822 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 823 newDocumentId); 824 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 825 826 } else if (METHOD_RENAME_DOCUMENT.equals(method)) { 827 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 828 829 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 830 final String newDocumentId = renameDocument(documentId, displayName); 831 832 if (newDocumentId != null) { 833 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 834 newDocumentId); 835 836 // If caller came in with a narrow grant, issue them a 837 // narrow grant for the newly renamed document. 838 if (!isTreeUri(newDocumentUri)) { 839 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, 840 documentUri); 841 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); 842 } 843 844 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 845 846 // Original document no longer exists, clean up any grants. 847 revokeDocumentPermission(documentId); 848 } 849 850 } else if (METHOD_DELETE_DOCUMENT.equals(method)) { 851 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 852 deleteDocument(documentId); 853 854 // Document no longer exists, clean up any grants. 855 revokeDocumentPermission(documentId); 856 857 } else if (METHOD_COPY_DOCUMENT.equals(method)) { 858 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI); 859 final String targetId = DocumentsContract.getDocumentId(targetUri); 860 861 enforceReadPermissionInner(documentUri, getCallingPackage(), null); 862 enforceWritePermissionInner(targetUri, getCallingPackage(), null); 863 864 final String newDocumentId = copyDocument(documentId, targetId); 865 866 if (newDocumentId != null) { 867 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 868 newDocumentId); 869 870 if (!isTreeUri(newDocumentUri)) { 871 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, 872 documentUri); 873 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); 874 } 875 876 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 877 } 878 879 } else if (METHOD_MOVE_DOCUMENT.equals(method)) { 880 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI); 881 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); 882 final Uri targetUri = extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI); 883 final String targetId = DocumentsContract.getDocumentId(targetUri); 884 885 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 886 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null); 887 enforceWritePermissionInner(targetUri, getCallingPackage(), null); 888 889 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId); 890 891 if (newDocumentId != null) { 892 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 893 newDocumentId); 894 895 if (!isTreeUri(newDocumentUri)) { 896 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, 897 documentUri); 898 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); 899 } 900 901 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 902 } 903 904 } else if (METHOD_REMOVE_DOCUMENT.equals(method)) { 905 final Uri parentSourceUri = extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI); 906 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); 907 908 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), null); 909 enforceWritePermissionInner(documentUri, getCallingPackage(), null); 910 removeDocument(documentId, parentSourceId); 911 912 // It's responsibility of the provider to revoke any grants, as the document may be 913 // still attached to another parents. 914 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) { 915 final boolean isTreeUri = isTreeUri(documentUri); 916 917 if (isTreeUri) { 918 enforceReadPermissionInner(documentUri, getCallingPackage(), null); 919 } else { 920 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null); 921 } 922 923 final String parentDocumentId = isTreeUri 924 ? DocumentsContract.getTreeDocumentId(documentUri) 925 : null; 926 927 Path path = findDocumentPath(documentId, parentDocumentId); 928 929 // Ensure provider doesn't leak information to unprivileged callers. 930 if (isTreeUri) { 931 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) { 932 Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: " 933 + parentDocumentId + " found: " + path.getPath().get(0)); 934 } 935 936 if (path.getRootId() != null) { 937 Log.wtf(TAG, "Provider returns root id :" 938 + path.getRootId() + " unexpectedly. Erase root id."); 939 path = new Path(null, path.getPath()); 940 } 941 } 942 943 out.putParcelable(DocumentsContract.EXTRA_RESULT, path); 944 } else { 945 throw new UnsupportedOperationException("Method not supported " + method); 946 } 947 948 return out; 949 } 950 951 /** 952 * Revoke any active permission grants for the given 953 * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document 954 * becomes invalid. Follows the same semantics as 955 * {@link Context#revokeUriPermission(Uri, int)}. 956 */ 957 public final void revokeDocumentPermission(String documentId) { 958 final Context context = getContext(); 959 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0); 960 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0); 961 } 962 963 /** 964 * Implementation is provided by the parent class. Cannot be overriden. 965 * 966 * @see #openDocument(String, String, CancellationSignal) 967 */ 968 @Override 969 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 970 enforceTree(uri); 971 return openDocument(getDocumentId(uri), mode, null); 972 } 973 974 /** 975 * Implementation is provided by the parent class. Cannot be overriden. 976 * 977 * @see #openDocument(String, String, CancellationSignal) 978 */ 979 @Override 980 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) 981 throws FileNotFoundException { 982 enforceTree(uri); 983 return openDocument(getDocumentId(uri), mode, signal); 984 } 985 986 /** 987 * Implementation is provided by the parent class. Cannot be overriden. 988 * 989 * @see #openDocument(String, String, CancellationSignal) 990 */ 991 @Override 992 @SuppressWarnings("resource") 993 public final AssetFileDescriptor openAssetFile(Uri uri, String mode) 994 throws FileNotFoundException { 995 enforceTree(uri); 996 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null); 997 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 998 } 999 1000 /** 1001 * Implementation is provided by the parent class. Cannot be overriden. 1002 * 1003 * @see #openDocument(String, String, CancellationSignal) 1004 */ 1005 @Override 1006 @SuppressWarnings("resource") 1007 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal) 1008 throws FileNotFoundException { 1009 enforceTree(uri); 1010 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal); 1011 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 1012 } 1013 1014 /** 1015 * Implementation is provided by the parent class. Cannot be overriden. 1016 * 1017 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 1018 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1019 * @see #getDocumentStreamTypes(String, String) 1020 */ 1021 @Override 1022 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) 1023 throws FileNotFoundException { 1024 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null); 1025 } 1026 1027 /** 1028 * Implementation is provided by the parent class. Cannot be overriden. 1029 * 1030 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 1031 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1032 * @see #getDocumentStreamTypes(String, String) 1033 */ 1034 @Override 1035 public final AssetFileDescriptor openTypedAssetFile( 1036 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 1037 throws FileNotFoundException { 1038 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal); 1039 } 1040 1041 /** 1042 * Return a list of streamable MIME types matching the filter, which can be passed to 1043 * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}. 1044 * 1045 * <p>The default implementation returns a MIME type provided by 1046 * {@link #queryDocument(String, String[])} as long as it matches the filter and the document 1047 * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set. 1048 * 1049 * <p>Virtual documents must have at least one streamable format. 1050 * 1051 * @see #getStreamTypes(Uri, String) 1052 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1053 */ 1054 public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) { 1055 Cursor cursor = null; 1056 try { 1057 cursor = queryDocument(documentId, null); 1058 if (cursor.moveToFirst()) { 1059 final String mimeType = 1060 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 1061 final long flags = 1062 cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS)); 1063 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null && 1064 mimeTypeMatches(mimeTypeFilter, mimeType)) { 1065 return new String[] { mimeType }; 1066 } 1067 } 1068 } catch (FileNotFoundException e) { 1069 return null; 1070 } finally { 1071 IoUtils.closeQuietly(cursor); 1072 } 1073 1074 // No streamable MIME types. 1075 return null; 1076 } 1077 1078 /** 1079 * Called by a client to determine the types of data streams that this content provider 1080 * support for the given URI. 1081 * 1082 * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead. 1083 * 1084 * @see #getDocumentStreamTypes(String, String) 1085 */ 1086 @Override 1087 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { 1088 enforceTree(uri); 1089 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter); 1090 } 1091 1092 /** 1093 * @hide 1094 */ 1095 private final AssetFileDescriptor openTypedAssetFileImpl( 1096 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 1097 throws FileNotFoundException { 1098 enforceTree(uri); 1099 final String documentId = getDocumentId(uri); 1100 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) { 1101 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE); 1102 return openDocumentThumbnail(documentId, sizeHint, signal); 1103 } 1104 if ("*/*".equals(mimeTypeFilter)) { 1105 // If they can take anything, the untyped open call is good enough. 1106 return openAssetFile(uri, "r"); 1107 } 1108 final String baseType = getType(uri); 1109 if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) { 1110 // Use old untyped open call if this provider has a type for this 1111 // URI and it matches the request. 1112 return openAssetFile(uri, "r"); 1113 } 1114 // For any other yet unhandled case, let the provider subclass handle it. 1115 return openTypedDocument(documentId, mimeTypeFilter, opts, signal); 1116 } 1117 1118 /** 1119 * @hide 1120 */ 1121 public static boolean mimeTypeMatches(String filter, String test) { 1122 if (test == null) { 1123 return false; 1124 } else if (filter == null || "*/*".equals(filter)) { 1125 return true; 1126 } else if (filter.equals(test)) { 1127 return true; 1128 } else if (filter.endsWith("/*")) { 1129 return filter.regionMatches(0, test, 0, filter.indexOf('/')); 1130 } else { 1131 return false; 1132 } 1133 } 1134} 1135