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