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