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