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