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