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