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