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