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