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_COPY_DOCUMENT; 20 import static android.provider.DocumentsContract.METHOD_CREATE_DOCUMENT; 21 import static android.provider.DocumentsContract.METHOD_CREATE_WEB_LINK_INTENT; 22 import static android.provider.DocumentsContract.METHOD_DELETE_DOCUMENT; 23 import static android.provider.DocumentsContract.METHOD_EJECT_ROOT; 24 import static android.provider.DocumentsContract.METHOD_FIND_DOCUMENT_PATH; 25 import static android.provider.DocumentsContract.METHOD_GET_DOCUMENT_METADATA; 26 import static android.provider.DocumentsContract.METHOD_IS_CHILD_DOCUMENT; 27 import static android.provider.DocumentsContract.METHOD_MOVE_DOCUMENT; 28 import static android.provider.DocumentsContract.METHOD_REMOVE_DOCUMENT; 29 import static android.provider.DocumentsContract.METHOD_RENAME_DOCUMENT; 30 import static android.provider.DocumentsContract.buildDocumentUri; 31 import static android.provider.DocumentsContract.buildDocumentUriMaybeUsingTree; 32 import static android.provider.DocumentsContract.buildTreeDocumentUri; 33 import static android.provider.DocumentsContract.getDocumentId; 34 import static android.provider.DocumentsContract.getRootId; 35 import static android.provider.DocumentsContract.getTreeDocumentId; 36 import static android.provider.DocumentsContract.isTreeUri; 37 38 import android.Manifest; 39 import android.annotation.CallSuper; 40 import android.annotation.NonNull; 41 import android.annotation.Nullable; 42 import android.app.AuthenticationRequiredException; 43 import android.content.ClipDescription; 44 import android.content.ContentProvider; 45 import android.content.ContentResolver; 46 import android.content.ContentValues; 47 import android.content.Context; 48 import android.content.Intent; 49 import android.content.IntentSender; 50 import android.content.MimeTypeFilter; 51 import android.content.UriMatcher; 52 import android.content.pm.PackageManager; 53 import android.content.pm.ProviderInfo; 54 import android.content.res.AssetFileDescriptor; 55 import android.database.Cursor; 56 import android.graphics.Point; 57 import android.net.Uri; 58 import android.os.Bundle; 59 import android.os.CancellationSignal; 60 import android.os.ParcelFileDescriptor; 61 import android.os.ParcelableException; 62 import android.provider.DocumentsContract.Document; 63 import android.provider.DocumentsContract.Path; 64 import android.provider.DocumentsContract.Root; 65 import android.util.Log; 66 67 import com.android.internal.util.Preconditions; 68 69 import libcore.io.IoUtils; 70 71 import java.io.FileNotFoundException; 72 import java.util.LinkedList; 73 import java.util.Objects; 74 75 /** 76 * Base class for a document provider. A document provider offers read and write 77 * access to durable files, such as files stored on a local disk, or files in a 78 * cloud storage service. To create a document provider, extend this class, 79 * implement the abstract methods, and add it to your manifest like this: 80 * 81 * <pre class="prettyprint"><manifest> 82 * ... 83 * <application> 84 * ... 85 * <provider 86 * android:name="com.example.MyCloudProvider" 87 * android:authorities="com.example.mycloudprovider" 88 * android:exported="true" 89 * android:grantUriPermissions="true" 90 * android:permission="android.permission.MANAGE_DOCUMENTS" 91 * android:enabled="@bool/isAtLeastKitKat"> 92 * <intent-filter> 93 * <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 94 * </intent-filter> 95 * </provider> 96 * ... 97 * </application> 98 *</manifest></pre> 99 * <p> 100 * When defining your provider, you must protect it with 101 * {@link android.Manifest.permission#MANAGE_DOCUMENTS}, which is a permission 102 * only the system can obtain. Applications cannot use a documents provider 103 * directly; they must go through {@link Intent#ACTION_OPEN_DOCUMENT} or 104 * {@link Intent#ACTION_CREATE_DOCUMENT} which requires a user to actively 105 * navigate and select documents. When a user selects documents through that UI, 106 * the system issues narrow URI permission grants to the requesting application. 107 * </p> 108 * <h3>Documents</h3> 109 * <p> 110 * A document can be either an openable stream (with a specific MIME type), or a 111 * directory containing additional documents (with the 112 * {@link Document#MIME_TYPE_DIR} MIME type). Each directory represents the top 113 * of a subtree containing zero or more documents, which can recursively contain 114 * even more documents and directories. 115 * </p> 116 * <p> 117 * Each document can have different capabilities, as described by 118 * {@link Document#COLUMN_FLAGS}. For example, if a document can be represented 119 * as a thumbnail, your provider can set 120 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} and implement 121 * {@link #openDocumentThumbnail(String, Point, CancellationSignal)} to return 122 * that thumbnail. 123 * </p> 124 * <p> 125 * Each document under a provider is uniquely referenced by its 126 * {@link Document#COLUMN_DOCUMENT_ID}, which must not change once returned. A 127 * single document can be included in multiple directories when responding to 128 * {@link #queryChildDocuments(String, String[], String)}. For example, a 129 * provider might surface a single photo in multiple locations: once in a 130 * directory of geographic locations, and again in a directory of dates. 131 * </p> 132 * <h3>Roots</h3> 133 * <p> 134 * All documents are surfaced through one or more "roots." Each root represents 135 * the top of a document tree that a user can navigate. For example, a root 136 * could represent an account or a physical storage device. Similar to 137 * documents, each root can have capabilities expressed through 138 * {@link Root#COLUMN_FLAGS}. 139 * </p> 140 * 141 * @see Intent#ACTION_OPEN_DOCUMENT 142 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 143 * @see Intent#ACTION_CREATE_DOCUMENT 144 */ 145 public abstract class DocumentsProvider extends ContentProvider { 146 private static final String TAG = "DocumentsProvider"; 147 148 private static final int MATCH_ROOTS = 1; 149 private static final int MATCH_ROOT = 2; 150 private static final int MATCH_RECENT = 3; 151 private static final int MATCH_SEARCH = 4; 152 private static final int MATCH_DOCUMENT = 5; 153 private static final int MATCH_CHILDREN = 6; 154 private static final int MATCH_DOCUMENT_TREE = 7; 155 private static final int MATCH_CHILDREN_TREE = 8; 156 157 private String mAuthority; 158 159 private UriMatcher mMatcher; 160 161 /** 162 * Implementation is provided by the parent class. 163 */ 164 @Override attachInfo(Context context, ProviderInfo info)165 public void attachInfo(Context context, ProviderInfo info) { 166 registerAuthority(info.authority); 167 168 // Sanity check our setup 169 if (!info.exported) { 170 throw new SecurityException("Provider must be exported"); 171 } 172 if (!info.grantUriPermissions) { 173 throw new SecurityException("Provider must grantUriPermissions"); 174 } 175 if (!android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.readPermission) 176 || !android.Manifest.permission.MANAGE_DOCUMENTS.equals(info.writePermission)) { 177 throw new SecurityException("Provider must be protected by MANAGE_DOCUMENTS"); 178 } 179 180 super.attachInfo(context, info); 181 } 182 183 /** {@hide} */ 184 @Override attachInfoForTesting(Context context, ProviderInfo info)185 public void attachInfoForTesting(Context context, ProviderInfo info) { 186 registerAuthority(info.authority); 187 188 super.attachInfoForTesting(context, info); 189 } 190 registerAuthority(String authority)191 private void registerAuthority(String authority) { 192 mAuthority = authority; 193 194 mMatcher = new UriMatcher(UriMatcher.NO_MATCH); 195 mMatcher.addURI(mAuthority, "root", MATCH_ROOTS); 196 mMatcher.addURI(mAuthority, "root/*", MATCH_ROOT); 197 mMatcher.addURI(mAuthority, "root/*/recent", MATCH_RECENT); 198 mMatcher.addURI(mAuthority, "root/*/search", MATCH_SEARCH); 199 mMatcher.addURI(mAuthority, "document/*", MATCH_DOCUMENT); 200 mMatcher.addURI(mAuthority, "document/*/children", MATCH_CHILDREN); 201 mMatcher.addURI(mAuthority, "tree/*/document/*", MATCH_DOCUMENT_TREE); 202 mMatcher.addURI(mAuthority, "tree/*/document/*/children", MATCH_CHILDREN_TREE); 203 } 204 205 /** 206 * Test if a document is descendant (child, grandchild, etc) from the given 207 * parent. For example, providers must implement this to support 208 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. You should avoid making network 209 * requests to keep this request fast. 210 * 211 * @param parentDocumentId parent to verify against. 212 * @param documentId child to verify. 213 * @return if given document is a descendant of the given parent. 214 * @see DocumentsContract.Root#FLAG_SUPPORTS_IS_CHILD 215 */ isChildDocument(String parentDocumentId, String documentId)216 public boolean isChildDocument(String parentDocumentId, String documentId) { 217 return false; 218 } 219 220 /** {@hide} */ enforceTreeForExtraUris(Bundle extras)221 private void enforceTreeForExtraUris(Bundle extras) { 222 enforceTree(extras.getParcelable(DocumentsContract.EXTRA_URI)); 223 enforceTree(extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI)); 224 enforceTree(extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI)); 225 } 226 227 /** {@hide} */ enforceTree(@ullable Uri documentUri)228 private void enforceTree(@Nullable Uri documentUri) { 229 if (documentUri != null && isTreeUri(documentUri)) { 230 final String parent = getTreeDocumentId(documentUri); 231 final String child = getDocumentId(documentUri); 232 if (Objects.equals(parent, child)) { 233 return; 234 } 235 if (!isChildDocument(parent, child)) { 236 throw new SecurityException( 237 "Document " + child + " is not a descendant of " + parent); 238 } 239 } 240 } 241 validateIncomingNullableUri(@ullable Uri uri)242 private Uri validateIncomingNullableUri(@Nullable Uri uri) { 243 return uri == null ? null : validateIncomingUri(uri); 244 } 245 246 /** 247 * Create a new document and return its newly generated 248 * {@link Document#COLUMN_DOCUMENT_ID}. You must allocate a new 249 * {@link Document#COLUMN_DOCUMENT_ID} to represent the document, which must 250 * not change once returned. 251 * 252 * @param parentDocumentId the parent directory to create the new document 253 * under. 254 * @param mimeType the concrete MIME type associated with the new document. 255 * If the MIME type is not supported, the provider must throw. 256 * @param displayName the display name of the new document. The provider may 257 * alter this name to meet any internal constraints, such as 258 * avoiding conflicting names. 259 260 * @throws AuthenticationRequiredException If authentication is required from the user (such as 261 * login credentials), but it is not guaranteed that the client will handle this 262 * properly. 263 */ 264 @SuppressWarnings("unused") createDocument(String parentDocumentId, String mimeType, String displayName)265 public String createDocument(String parentDocumentId, String mimeType, String displayName) 266 throws FileNotFoundException { 267 throw new UnsupportedOperationException("Create not supported"); 268 } 269 270 /** 271 * Rename an existing document. 272 * <p> 273 * If a different {@link Document#COLUMN_DOCUMENT_ID} must be used to 274 * represent the renamed document, generate and return it. Any outstanding 275 * URI permission grants will be updated to point at the new document. If 276 * the original {@link Document#COLUMN_DOCUMENT_ID} is still valid after the 277 * rename, return {@code null}. 278 * 279 * @param documentId the document to rename. 280 * @param displayName the updated display name of the document. The provider 281 * may alter this name to meet any internal constraints, such as 282 * avoiding conflicting names. 283 * @throws AuthenticationRequiredException If authentication is required from 284 * the user (such as login credentials), but it is not guaranteed 285 * that the client will handle this properly. 286 */ 287 @SuppressWarnings("unused") renameDocument(String documentId, String displayName)288 public String renameDocument(String documentId, String displayName) 289 throws FileNotFoundException { 290 throw new UnsupportedOperationException("Rename not supported"); 291 } 292 293 /** 294 * Delete the requested document. 295 * <p> 296 * Upon returning, any URI permission grants for the given document will be 297 * revoked. If additional documents were deleted as a side effect of this 298 * call (such as documents inside a directory) the implementor is 299 * responsible for revoking those permissions using 300 * {@link #revokeDocumentPermission(String)}. 301 * 302 * @param documentId the document to delete. 303 * @throws AuthenticationRequiredException If authentication is required from 304 * the user (such as login credentials), but it is not guaranteed 305 * that the client will handle this properly. 306 */ 307 @SuppressWarnings("unused") deleteDocument(String documentId)308 public void deleteDocument(String documentId) throws FileNotFoundException { 309 throw new UnsupportedOperationException("Delete not supported"); 310 } 311 312 /** 313 * Copy the requested document or a document tree. 314 * <p> 315 * Copies a document including all child documents to another location within 316 * the same document provider. Upon completion returns the document id of 317 * the copied document at the target destination. {@code null} must never 318 * be returned. 319 * 320 * @param sourceDocumentId the document to copy. 321 * @param targetParentDocumentId the target document to be copied into as a child. 322 * @throws AuthenticationRequiredException If authentication is required from 323 * the user (such as login credentials), but it is not guaranteed 324 * that the client will handle this properly. 325 */ 326 @SuppressWarnings("unused") copyDocument(String sourceDocumentId, String targetParentDocumentId)327 public String copyDocument(String sourceDocumentId, String targetParentDocumentId) 328 throws FileNotFoundException { 329 throw new UnsupportedOperationException("Copy not supported"); 330 } 331 332 /** 333 * Move the requested document or a document tree. 334 * 335 * <p>Moves a document including all child documents to another location within 336 * the same document provider. Upon completion returns the document id of 337 * the copied document at the target destination. {@code null} must never 338 * be returned. 339 * 340 * <p>It's the responsibility of the provider to revoke grants if the document 341 * is no longer accessible using <code>sourceDocumentId</code>. 342 * 343 * @param sourceDocumentId the document to move. 344 * @param sourceParentDocumentId the parent of the document to move. 345 * @param targetParentDocumentId the target document to be a new parent of the 346 * source document. 347 * @throws AuthenticationRequiredException If authentication is required from 348 * the user (such as login credentials), but it is not guaranteed 349 * that the client will handle this properly. 350 */ 351 @SuppressWarnings("unused") moveDocument(String sourceDocumentId, String sourceParentDocumentId, String targetParentDocumentId)352 public String moveDocument(String sourceDocumentId, String sourceParentDocumentId, 353 String targetParentDocumentId) 354 throws FileNotFoundException { 355 throw new UnsupportedOperationException("Move not supported"); 356 } 357 358 /** 359 * Removes the requested document or a document tree. 360 * 361 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent. 362 * This method is especially useful if the document can be in multiple parents. 363 * 364 * <p>It's the responsibility of the provider to revoke grants if the document is 365 * removed from the last parent, and effectively the document is deleted. 366 * 367 * @param documentId the document to remove. 368 * @param parentDocumentId the parent of the document to move. 369 * @throws AuthenticationRequiredException If authentication is required from 370 * the user (such as login credentials), but it is not guaranteed 371 * that the client will handle this properly. 372 */ 373 @SuppressWarnings("unused") removeDocument(String documentId, String parentDocumentId)374 public void removeDocument(String documentId, String parentDocumentId) 375 throws FileNotFoundException { 376 throw new UnsupportedOperationException("Remove not supported"); 377 } 378 379 /** 380 * Finds the canonical path for the requested document. The path must start 381 * from the parent document if parentDocumentId is not null or the root document 382 * if parentDocumentId is null. If there are more than one path to this document, 383 * return the most typical one. Include both the parent document or root document 384 * and the requested document in the returned path. 385 * 386 * <p>This API assumes that document ID has enough info to infer the root. 387 * Different roots should use different document ID to refer to the same 388 * document. 389 * 390 * 391 * @param parentDocumentId the document from which the path starts if not null, 392 * or null to indicate a path from the root is requested. 393 * @param childDocumentId the document which path is requested. 394 * @return the path of the requested document. If parentDocumentId is null 395 * returned root ID must not be null. If parentDocumentId is not null 396 * returned root ID must be null. 397 * @throws AuthenticationRequiredException If authentication is required from 398 * the user (such as login credentials), but it is not guaranteed 399 * that the client will handle this properly. 400 */ findDocumentPath(@ullable String parentDocumentId, String childDocumentId)401 public Path findDocumentPath(@Nullable String parentDocumentId, String childDocumentId) 402 throws FileNotFoundException { 403 throw new UnsupportedOperationException("findDocumentPath not supported."); 404 } 405 406 /** 407 * Creates an intent sender for a web link, if the document is web linkable. 408 * <p> 409 * {@link AuthenticationRequiredException} can be thrown if user does not have 410 * sufficient permission for the linked document. Before any new permissions 411 * are granted for the linked document, a visible UI must be shown, so the 412 * user can explicitly confirm whether the permission grants are expected. 413 * The user must be able to cancel the operation. 414 * <p> 415 * Options passed as an argument may include a list of recipients, such 416 * as email addresses. The provider should reflect these options if possible, 417 * but it's acceptable to ignore them. In either case, confirmation UI must 418 * be shown before any new permission grants are granted. 419 * <p> 420 * It is all right to generate a web link without granting new permissions, 421 * if opening the link would result in a page for requesting permission 422 * access. If it's impossible then the operation must fail by throwing an exception. 423 * 424 * @param documentId the document to create a web link intent for. 425 * @param options additional information, such as list of recipients. Optional. 426 * @throws AuthenticationRequiredException If authentication is required from 427 * the user (such as login credentials), but it is not guaranteed 428 * that the client will handle this properly. 429 * 430 * @see DocumentsContract.Document#FLAG_WEB_LINKABLE 431 * @see android.app.PendingIntent#getIntentSender 432 */ createWebLinkIntent(String documentId, @Nullable Bundle options)433 public IntentSender createWebLinkIntent(String documentId, @Nullable Bundle options) 434 throws FileNotFoundException { 435 throw new UnsupportedOperationException("createWebLink is not supported."); 436 } 437 438 /** 439 * Return all roots currently provided. To display to users, you must define 440 * at least one root. You should avoid making network requests to keep this 441 * request fast. 442 * <p> 443 * Each root is defined by the metadata columns described in {@link Root}, 444 * including {@link Root#COLUMN_DOCUMENT_ID} which points to a directory 445 * representing a tree of documents to display under that root. 446 * <p> 447 * If this set of roots changes, you must call {@link ContentResolver#notifyChange(Uri, 448 * android.database.ContentObserver, boolean)} with 449 * {@link DocumentsContract#buildRootsUri(String)} to notify the system. 450 * <p> 451 * 452 * @param projection list of {@link Root} columns to put into the cursor. If 453 * {@code null} all supported columns should be included. 454 */ queryRoots(String[] projection)455 public abstract Cursor queryRoots(String[] projection) throws FileNotFoundException; 456 457 /** 458 * Return recently modified documents under the requested root. This will 459 * only be called for roots that advertise 460 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be 461 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order, and 462 * limited to only return the 64 most recently modified documents. 463 * <p> 464 * Recent documents do not support change notifications. 465 * 466 * @param projection list of {@link Document} columns to put into the 467 * cursor. If {@code null} all supported columns should be 468 * included. 469 * @see DocumentsContract#EXTRA_LOADING 470 */ 471 @SuppressWarnings("unused") queryRecentDocuments(String rootId, String[] projection)472 public Cursor queryRecentDocuments(String rootId, String[] projection) 473 throws FileNotFoundException { 474 throw new UnsupportedOperationException("Recent not supported"); 475 } 476 477 /** 478 * Return recently modified documents under the requested root. This will 479 * only be called for roots that advertise 480 * {@link Root#FLAG_SUPPORTS_RECENTS}. The returned documents should be 481 * sorted by {@link Document#COLUMN_LAST_MODIFIED} in descending order of 482 * the most recently modified documents. 483 * <p> 484 * If this method is overriden by the concrete DocumentsProvider and 485 * {@link ContentResolver#QUERY_ARG_LIMIT} is specified with a nonnegative 486 * int under queryArgs, the result will be limited by that number and 487 * {@link ContentResolver#QUERY_ARG_LIMIT} will be specified under 488 * {@link ContentResolver#EXTRA_HONORED_ARGS}. Otherwise, a default 64 limit 489 * will be used and no QUERY_ARG* will be specified under 490 * {@link ContentResolver#EXTRA_HONORED_ARGS}. 491 * <p> 492 * Recent documents do not support change notifications. 493 * 494 * @param projection list of {@link Document} columns to put into the 495 * cursor. If {@code null} all supported columns should be 496 * included. 497 * @param queryArgs the extra query arguments. 498 * @param signal used by the caller to signal if the request should be 499 * cancelled. May be null. 500 * @see DocumentsContract#EXTRA_LOADING 501 */ 502 @SuppressWarnings("unused") 503 @Nullable queryRecentDocuments( @onNull String rootId, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal signal)504 public Cursor queryRecentDocuments( 505 @NonNull String rootId, @Nullable String[] projection, @Nullable Bundle queryArgs, 506 @Nullable CancellationSignal signal) throws FileNotFoundException { 507 Preconditions.checkNotNull(rootId, "rootId can not be null"); 508 509 Cursor c = queryRecentDocuments(rootId, projection); 510 Bundle extras = new Bundle(); 511 c.setExtras(extras); 512 extras.putStringArray(ContentResolver.EXTRA_HONORED_ARGS, new String[0]); 513 return c; 514 } 515 516 /** 517 * Return metadata for the single requested document. You should avoid 518 * making network requests to keep this request fast. 519 * 520 * @param documentId the document to return. 521 * @param projection list of {@link Document} columns to put into the 522 * cursor. If {@code null} all supported columns should be 523 * included. 524 * @throws AuthenticationRequiredException If authentication is required from 525 * the user (such as login credentials), but it is not guaranteed 526 * that the client will handle this properly. 527 */ queryDocument(String documentId, String[] projection)528 public abstract Cursor queryDocument(String documentId, String[] projection) 529 throws FileNotFoundException; 530 531 /** 532 * Return the children documents contained in the requested directory. This 533 * must only return immediate descendants, as additional queries will be 534 * issued to recursively explore the tree. 535 * <p> 536 * Apps targeting {@link android.os.Build.VERSION_CODES#O} or higher 537 * should override {@link #queryChildDocuments(String, String[], Bundle)}. 538 * <p> 539 * If your provider is cloud-based, and you have some data cached or pinned 540 * locally, you may return the local data immediately, setting 541 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that 542 * you are still fetching additional data. Then, when the network data is 543 * available, you can send a change notification to trigger a requery and 544 * return the complete contents. To return a Cursor with extras, you need to 545 * extend and override {@link Cursor#getExtras()}. 546 * <p> 547 * To support change notifications, you must 548 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant 549 * Uri, such as 550 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then 551 * you can call {@link ContentResolver#notifyChange(Uri, 552 * android.database.ContentObserver, boolean)} with that Uri to send change 553 * notifications. 554 * 555 * @param parentDocumentId the directory to return children for. 556 * @param projection list of {@link Document} columns to put into the 557 * cursor. If {@code null} all supported columns should be 558 * included. 559 * @param sortOrder how to order the rows, formatted as an SQL 560 * {@code ORDER BY} clause (excluding the ORDER BY itself). 561 * Passing {@code null} will use the default sort order, which 562 * may be unordered. This ordering is a hint that can be used to 563 * prioritize how data is fetched from the network, but UI may 564 * always enforce a specific ordering. 565 * @throws AuthenticationRequiredException If authentication is required from 566 * the user (such as login credentials), but it is not guaranteed 567 * that the client will handle this properly. 568 * @see DocumentsContract#EXTRA_LOADING 569 * @see DocumentsContract#EXTRA_INFO 570 * @see DocumentsContract#EXTRA_ERROR 571 */ queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder)572 public abstract Cursor queryChildDocuments( 573 String parentDocumentId, String[] projection, String sortOrder) 574 throws FileNotFoundException; 575 576 /** 577 * Override this method to return the children documents contained 578 * in the requested directory. This must return immediate descendants only. 579 * 580 * <p>If your provider is cloud-based, and you have data cached 581 * locally, you may return the local data immediately, setting 582 * {@link DocumentsContract#EXTRA_LOADING} on Cursor extras to indicate that 583 * you are still fetching additional data. Then, when the network data is 584 * available, you can send a change notification to trigger a requery and 585 * return the complete contents. To return a Cursor with extras, you need to 586 * extend and override {@link Cursor#getExtras()}. 587 * 588 * <p>To support change notifications, you must 589 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant 590 * Uri, such as 591 * {@link DocumentsContract#buildChildDocumentsUri(String, String)}. Then 592 * you can call {@link ContentResolver#notifyChange(Uri, 593 * android.database.ContentObserver, boolean)} with that Uri to send change 594 * notifications. 595 * 596 * @param parentDocumentId the directory to return children for. 597 * @param projection list of {@link Document} columns to put into the 598 * cursor. If {@code null} all supported columns should be 599 * included. 600 * @param queryArgs Bundle containing sorting information or other 601 * argument useful to the provider. If no sorting 602 * information is available, default sorting 603 * will be used, which may be unordered. See 604 * {@link ContentResolver#QUERY_ARG_SORT_COLUMNS} for 605 * details. 606 * @throws AuthenticationRequiredException If authentication is required from 607 * the user (such as login credentials), but it is not guaranteed 608 * that the client will handle this properly. 609 * 610 * @see DocumentsContract#EXTRA_LOADING 611 * @see DocumentsContract#EXTRA_INFO 612 * @see DocumentsContract#EXTRA_ERROR 613 */ queryChildDocuments( String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs)614 public Cursor queryChildDocuments( 615 String parentDocumentId, @Nullable String[] projection, @Nullable Bundle queryArgs) 616 throws FileNotFoundException { 617 618 return queryChildDocuments( 619 parentDocumentId, projection, getSortClause(queryArgs)); 620 } 621 622 /** {@hide} */ 623 @SuppressWarnings("unused") queryChildDocumentsForManage( String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder)624 public Cursor queryChildDocumentsForManage( 625 String parentDocumentId, @Nullable String[] projection, @Nullable String sortOrder) 626 throws FileNotFoundException { 627 throw new UnsupportedOperationException("Manage not supported"); 628 } 629 630 /** 631 * Return documents that match the given query under the requested 632 * root. The returned documents should be sorted by relevance in descending 633 * order. How documents are matched against the query string is an 634 * implementation detail left to each provider, but it's suggested that at 635 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a 636 * case-insensitive fashion. 637 * <p> 638 * If your provider is cloud-based, and you have some data cached or pinned 639 * locally, you may return the local data immediately, setting 640 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that 641 * you are still fetching additional data. Then, when the network data is 642 * available, you can send a change notification to trigger a requery and 643 * return the complete contents. 644 * <p> 645 * To support change notifications, you must 646 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant 647 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String, 648 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri, 649 * android.database.ContentObserver, boolean)} with that Uri to send change 650 * notifications. 651 * 652 * @param rootId the root to search under. 653 * @param query string to match documents against. 654 * @param projection list of {@link Document} columns to put into the 655 * cursor. If {@code null} all supported columns should be 656 * included. 657 * @throws AuthenticationRequiredException If authentication is required from 658 * the user (such as login credentials), but it is not guaranteed 659 * that the client will handle this properly. 660 * 661 * @see DocumentsContract#EXTRA_LOADING 662 * @see DocumentsContract#EXTRA_INFO 663 * @see DocumentsContract#EXTRA_ERROR 664 */ 665 @SuppressWarnings("unused") querySearchDocuments(String rootId, String query, String[] projection)666 public Cursor querySearchDocuments(String rootId, String query, String[] projection) 667 throws FileNotFoundException { 668 throw new UnsupportedOperationException("Search not supported"); 669 } 670 671 /** 672 * Return documents that match the given query under the requested 673 * root. The returned documents should be sorted by relevance in descending 674 * order. How documents are matched against the query string is an 675 * implementation detail left to each provider, but it's suggested that at 676 * least {@link Document#COLUMN_DISPLAY_NAME} be matched in a 677 * case-insensitive fashion. 678 * <p> 679 * If your provider is cloud-based, and you have some data cached or pinned 680 * locally, you may return the local data immediately, setting 681 * {@link DocumentsContract#EXTRA_LOADING} on the Cursor to indicate that 682 * you are still fetching additional data. Then, when the network data is 683 * available, you can send a change notification to trigger a requery and 684 * return the complete contents. 685 * <p> 686 * To support change notifications, you must 687 * {@link Cursor#setNotificationUri(ContentResolver, Uri)} with a relevant 688 * Uri, such as {@link DocumentsContract#buildSearchDocumentsUri(String, 689 * String, String)}. Then you can call {@link ContentResolver#notifyChange(Uri, 690 * android.database.ContentObserver, boolean)} with that Uri to send change 691 * notifications. 692 * 693 * @param rootId the root to search under. 694 * @param projection list of {@link Document} columns to put into the 695 * cursor. If {@code null} all supported columns should be 696 * included. 697 * @param queryArgs the query arguments. 698 * {@link DocumentsContract#QUERY_ARG_EXCLUDE_MEDIA}, 699 * {@link DocumentsContract#QUERY_ARG_DISPLAY_NAME}, 700 * {@link DocumentsContract#QUERY_ARG_MIME_TYPES}, 701 * {@link DocumentsContract#QUERY_ARG_FILE_SIZE_OVER}, 702 * {@link DocumentsContract#QUERY_ARG_LAST_MODIFIED_AFTER}. 703 * @return cursor containing search result. Include 704 * {@link ContentResolver#EXTRA_HONORED_ARGS} in {@link Cursor} 705 * extras {@link Bundle} when any QUERY_ARG_* value was honored 706 * during the preparation of the results. 707 * 708 * @see Root#COLUMN_QUERY_ARGS 709 * @see ContentResolver#EXTRA_HONORED_ARGS 710 * @see DocumentsContract#EXTRA_LOADING 711 * @see DocumentsContract#EXTRA_INFO 712 * @see DocumentsContract#EXTRA_ERROR 713 */ 714 @SuppressWarnings("unused") 715 @Nullable querySearchDocuments(@onNull String rootId, @Nullable String[] projection, @NonNull Bundle queryArgs)716 public Cursor querySearchDocuments(@NonNull String rootId, 717 @Nullable String[] projection, @NonNull Bundle queryArgs) throws FileNotFoundException { 718 Preconditions.checkNotNull(rootId, "rootId can not be null"); 719 Preconditions.checkNotNull(queryArgs, "queryArgs can not be null"); 720 return querySearchDocuments(rootId, DocumentsContract.getSearchDocumentsQuery(queryArgs), 721 projection); 722 } 723 724 /** 725 * Ejects the root. Throws {@link IllegalStateException} if ejection failed. 726 * 727 * @param rootId the root to be ejected. 728 * @see Root#FLAG_SUPPORTS_EJECT 729 */ 730 @SuppressWarnings("unused") ejectRoot(String rootId)731 public void ejectRoot(String rootId) { 732 throw new UnsupportedOperationException("Eject not supported"); 733 } 734 735 /** 736 * Returns metadata associated with the document. The type of metadata returned 737 * is specific to the document type. For example the data returned for an image 738 * file will likely consist primarily or solely of EXIF metadata. 739 * 740 * <p>The returned {@link Bundle} will contain zero or more entries depending 741 * on the type of data supported by the document provider. 742 * 743 * <ol> 744 * <li>A {@link DocumentsContract#METADATA_TYPES} containing a {@code String[]} value. 745 * The string array identifies the type or types of metadata returned. Each 746 * value in the can be used to access a {@link Bundle} of data 747 * containing that type of data. 748 * <li>An entry each for each type of returned metadata. Each set of metadata is 749 * itself represented as a bundle and accessible via a string key naming 750 * the type of data. 751 * </ol> 752 * 753 * @param documentId get the metadata of the document 754 * @return a Bundle of Bundles. 755 * @see DocumentsContract#getDocumentMetadata(ContentResolver, Uri) 756 */ getDocumentMetadata(@onNull String documentId)757 public @Nullable Bundle getDocumentMetadata(@NonNull String documentId) 758 throws FileNotFoundException { 759 throw new UnsupportedOperationException("Metadata not supported"); 760 } 761 762 /** 763 * Return concrete MIME type of the requested document. Must match the value 764 * of {@link Document#COLUMN_MIME_TYPE} for this document. The default 765 * implementation queries {@link #queryDocument(String, String[])}, so 766 * providers may choose to override this as an optimization. 767 * <p> 768 * @throws AuthenticationRequiredException If authentication is required from 769 * the user (such as login credentials), but it is not guaranteed 770 * that the client will handle this properly. 771 */ getDocumentType(String documentId)772 public String getDocumentType(String documentId) throws FileNotFoundException { 773 final Cursor cursor = queryDocument(documentId, null); 774 try { 775 if (cursor.moveToFirst()) { 776 return cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 777 } else { 778 return null; 779 } 780 } finally { 781 IoUtils.closeQuietly(cursor); 782 } 783 } 784 785 /** 786 * Open and return the requested document. 787 * <p> 788 * Your provider should return a reliable {@link ParcelFileDescriptor} to 789 * detect when the remote caller has finished reading or writing the 790 * document. 791 * <p> 792 * Mode "r" should always be supported. Provider should throw 793 * {@link UnsupportedOperationException} if the passing mode is not supported. 794 * You may return a pipe or socket pair if the mode is exclusively "r" or 795 * "w", but complex modes like "rw" imply a normal file on disk that 796 * supports seeking. 797 * <p> 798 * If you block while downloading content, you should periodically check 799 * {@link CancellationSignal#isCanceled()} to abort abandoned open requests. 800 * 801 * @param documentId the document to return. 802 * @param mode the mode to open with, such as 'r', 'w', or 'rw'. 803 * @param signal used by the caller to signal if the request should be 804 * cancelled. May be null. 805 * @throws AuthenticationRequiredException If authentication is required from 806 * the user (such as login credentials), but it is not guaranteed 807 * that the client will handle this properly. 808 * @see ParcelFileDescriptor#open(java.io.File, int, android.os.Handler, 809 * OnCloseListener) 810 * @see ParcelFileDescriptor#createReliablePipe() 811 * @see ParcelFileDescriptor#createReliableSocketPair() 812 * @see ParcelFileDescriptor#parseMode(String) 813 */ openDocument( String documentId, String mode, @Nullable CancellationSignal signal)814 public abstract ParcelFileDescriptor openDocument( 815 String documentId, 816 String mode, 817 @Nullable CancellationSignal signal) throws FileNotFoundException; 818 819 /** 820 * Open and return a thumbnail of the requested document. 821 * <p> 822 * A provider should return a thumbnail closely matching the hinted size, 823 * attempting to serve from a local cache if possible. A provider should 824 * never return images more than double the hinted size. 825 * <p> 826 * If you perform expensive operations to download or generate a thumbnail, 827 * you should periodically check {@link CancellationSignal#isCanceled()} to 828 * abort abandoned thumbnail requests. 829 * 830 * @param documentId the document to return. 831 * @param sizeHint hint of the optimal thumbnail dimensions. 832 * @param signal used by the caller to signal if the request should be 833 * cancelled. May be null. 834 * @throws AuthenticationRequiredException If authentication is required from 835 * the user (such as login credentials), but it is not guaranteed 836 * that the client will handle this properly. 837 * @see Document#FLAG_SUPPORTS_THUMBNAIL 838 */ 839 @SuppressWarnings("unused") openDocumentThumbnail( String documentId, Point sizeHint, CancellationSignal signal)840 public AssetFileDescriptor openDocumentThumbnail( 841 String documentId, Point sizeHint, CancellationSignal signal) 842 throws FileNotFoundException { 843 throw new UnsupportedOperationException("Thumbnails not supported"); 844 } 845 846 /** 847 * Open and return the document in a format matching the specified MIME 848 * type filter. 849 * <p> 850 * A provider may perform a conversion if the documents's MIME type is not 851 * matching the specified MIME type filter. 852 * <p> 853 * Virtual documents must have at least one streamable format. 854 * 855 * @param documentId the document to return. 856 * @param mimeTypeFilter the MIME type filter for the requested format. May 857 * be *\/*, which matches any MIME type. 858 * @param opts extra options from the client. Specific to the content 859 * provider. 860 * @param signal used by the caller to signal if the request should be 861 * cancelled. May be null. 862 * @throws AuthenticationRequiredException If authentication is required from 863 * the user (such as login credentials), but it is not guaranteed 864 * that the client will handle this properly. 865 * @see #getDocumentStreamTypes(String, String) 866 */ 867 @SuppressWarnings("unused") openTypedDocument( String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal)868 public AssetFileDescriptor openTypedDocument( 869 String documentId, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 870 throws FileNotFoundException { 871 throw new FileNotFoundException("The requested MIME type is not supported."); 872 } 873 874 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)875 public final Cursor query(Uri uri, String[] projection, String selection, 876 String[] selectionArgs, String sortOrder) { 877 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary 878 // transport method. We override that, and don't ever delegate to this method. 879 throw new UnsupportedOperationException("Pre-Android-O query format not supported."); 880 } 881 882 /** 883 * WARNING: Sub-classes should not override this method. This method is non-final 884 * solely for the purposes of backwards compatibility. 885 * 886 * @see #queryChildDocuments(String, String[], Bundle), 887 * {@link #queryDocument(String, String[])}, 888 * {@link #queryRecentDocuments(String, String[])}, 889 * {@link #queryRoots(String[])}, and 890 * {@link #querySearchDocuments(String, String[], Bundle)}. 891 */ 892 @Override query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal)893 public Cursor query(Uri uri, String[] projection, String selection, 894 String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { 895 // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary 896 // transport method. We override that, and don't ever delegate to this metohd. 897 throw new UnsupportedOperationException("Pre-Android-O query format not supported."); 898 } 899 900 /** 901 * Implementation is provided by the parent class. Cannot be overridden. 902 * 903 * @see #queryRoots(String[]) 904 * @see #queryRecentDocuments(String, String[], Bundle, CancellationSignal) 905 * @see #queryDocument(String, String[]) 906 * @see #queryChildDocuments(String, String[], String) 907 * @see #querySearchDocuments(String, String[], Bundle) 908 */ 909 @Override query( Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal)910 public final Cursor query( 911 Uri uri, String[] projection, Bundle queryArgs, CancellationSignal cancellationSignal) { 912 try { 913 switch (mMatcher.match(uri)) { 914 case MATCH_ROOTS: 915 return queryRoots(projection); 916 case MATCH_RECENT: 917 return queryRecentDocuments( 918 getRootId(uri), projection, queryArgs, cancellationSignal); 919 case MATCH_SEARCH: 920 return querySearchDocuments(getRootId(uri), projection, queryArgs); 921 case MATCH_DOCUMENT: 922 case MATCH_DOCUMENT_TREE: 923 enforceTree(uri); 924 return queryDocument(getDocumentId(uri), projection); 925 case MATCH_CHILDREN: 926 case MATCH_CHILDREN_TREE: 927 enforceTree(uri); 928 if (DocumentsContract.isManageMode(uri)) { 929 // TODO: Update "ForManage" variant to support query args. 930 return queryChildDocumentsForManage( 931 getDocumentId(uri), 932 projection, 933 getSortClause(queryArgs)); 934 } else { 935 return queryChildDocuments(getDocumentId(uri), projection, queryArgs); 936 } 937 default: 938 throw new UnsupportedOperationException("Unsupported Uri " + uri); 939 } 940 } catch (FileNotFoundException e) { 941 Log.w(TAG, "Failed during query", e); 942 return null; 943 } 944 } 945 getSortClause(@ullable Bundle queryArgs)946 private static @Nullable String getSortClause(@Nullable Bundle queryArgs) { 947 queryArgs = queryArgs != null ? queryArgs : Bundle.EMPTY; 948 String sortClause = queryArgs.getString(ContentResolver.QUERY_ARG_SQL_SORT_ORDER); 949 950 if (sortClause == null && queryArgs.containsKey(ContentResolver.QUERY_ARG_SORT_COLUMNS)) { 951 sortClause = ContentResolver.createSqlSortClause(queryArgs); 952 } 953 954 return sortClause; 955 } 956 957 /** 958 * Implementation is provided by the parent class. Cannot be overridden. 959 * 960 * @see #getDocumentType(String) 961 */ 962 @Override getType(Uri uri)963 public final String getType(Uri uri) { 964 try { 965 switch (mMatcher.match(uri)) { 966 case MATCH_ROOT: 967 return DocumentsContract.Root.MIME_TYPE_ITEM; 968 case MATCH_DOCUMENT: 969 case MATCH_DOCUMENT_TREE: 970 enforceTree(uri); 971 return getDocumentType(getDocumentId(uri)); 972 default: 973 return null; 974 } 975 } catch (FileNotFoundException e) { 976 Log.w(TAG, "Failed during getType", e); 977 return null; 978 } 979 } 980 981 /** 982 * Implementation is provided by the parent class. Can be overridden to 983 * provide additional functionality, but subclasses <em>must</em> always 984 * call the superclass. If the superclass returns {@code null}, the subclass 985 * may implement custom behavior. 986 * <p> 987 * This is typically used to resolve a subtree URI into a concrete document 988 * reference, issuing a narrower single-document URI permission grant along 989 * the way. 990 * 991 * @see DocumentsContract#buildDocumentUriUsingTree(Uri, String) 992 */ 993 @CallSuper 994 @Override canonicalize(Uri uri)995 public Uri canonicalize(Uri uri) { 996 final Context context = getContext(); 997 switch (mMatcher.match(uri)) { 998 case MATCH_DOCUMENT_TREE: 999 enforceTree(uri); 1000 1001 final Uri narrowUri = buildDocumentUri(uri.getAuthority(), getDocumentId(uri)); 1002 1003 // Caller may only have prefix grant, so extend them a grant to 1004 // the narrow URI. 1005 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, uri); 1006 context.grantUriPermission(getCallingPackage(), narrowUri, modeFlags); 1007 return narrowUri; 1008 } 1009 return null; 1010 } 1011 getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri)1012 private static int getCallingOrSelfUriPermissionModeFlags(Context context, Uri uri) { 1013 // TODO: move this to a direct AMS call 1014 int modeFlags = 0; 1015 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION) 1016 == PackageManager.PERMISSION_GRANTED) { 1017 modeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION; 1018 } 1019 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) 1020 == PackageManager.PERMISSION_GRANTED) { 1021 modeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; 1022 } 1023 if (context.checkCallingOrSelfUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION 1024 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) 1025 == PackageManager.PERMISSION_GRANTED) { 1026 modeFlags |= Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION; 1027 } 1028 return modeFlags; 1029 } 1030 1031 /** 1032 * Implementation is provided by the parent class. Throws by default, and 1033 * cannot be overridden. 1034 * 1035 * @see #createDocument(String, String, String) 1036 */ 1037 @Override insert(Uri uri, ContentValues values)1038 public final Uri insert(Uri uri, ContentValues values) { 1039 throw new UnsupportedOperationException("Insert not supported"); 1040 } 1041 1042 /** 1043 * Implementation is provided by the parent class. Throws by default, and 1044 * cannot be overridden. 1045 * 1046 * @see #deleteDocument(String) 1047 */ 1048 @Override delete(Uri uri, String selection, String[] selectionArgs)1049 public final int delete(Uri uri, String selection, String[] selectionArgs) { 1050 throw new UnsupportedOperationException("Delete not supported"); 1051 } 1052 1053 /** 1054 * Implementation is provided by the parent class. Throws by default, and 1055 * cannot be overridden. 1056 */ 1057 @Override update( Uri uri, ContentValues values, String selection, String[] selectionArgs)1058 public final int update( 1059 Uri uri, ContentValues values, String selection, String[] selectionArgs) { 1060 throw new UnsupportedOperationException("Update not supported"); 1061 } 1062 1063 /** 1064 * Implementation is provided by the parent class. Can be overridden to 1065 * provide additional functionality, but subclasses <em>must</em> always 1066 * call the superclass. If the superclass returns {@code null}, the subclass 1067 * may implement custom behavior. 1068 */ 1069 @CallSuper 1070 @Override call(String method, String arg, Bundle extras)1071 public Bundle call(String method, String arg, Bundle extras) { 1072 if (!method.startsWith("android:")) { 1073 // Ignore non-platform methods 1074 return super.call(method, arg, extras); 1075 } 1076 1077 try { 1078 return callUnchecked(method, arg, extras); 1079 } catch (FileNotFoundException e) { 1080 throw new ParcelableException(e); 1081 } 1082 } 1083 callUnchecked(String method, String arg, Bundle extras)1084 private Bundle callUnchecked(String method, String arg, Bundle extras) 1085 throws FileNotFoundException { 1086 1087 final Context context = getContext(); 1088 final Bundle out = new Bundle(); 1089 1090 // If the URI is a tree URI performs some validation. 1091 enforceTreeForExtraUris(extras); 1092 1093 final Uri extraUri = validateIncomingNullableUri( 1094 extras.getParcelable(DocumentsContract.EXTRA_URI)); 1095 final Uri extraTargetUri = validateIncomingNullableUri( 1096 extras.getParcelable(DocumentsContract.EXTRA_TARGET_URI)); 1097 final Uri extraParentUri = validateIncomingNullableUri( 1098 extras.getParcelable(DocumentsContract.EXTRA_PARENT_URI)); 1099 1100 if (METHOD_EJECT_ROOT.equals(method)) { 1101 // Given that certain system apps can hold MOUNT_UNMOUNT permission, but only apps 1102 // signed with platform signature can hold MANAGE_DOCUMENTS, we are going to check for 1103 // MANAGE_DOCUMENTS or associated URI permission here instead 1104 final Uri rootUri = extraUri; 1105 enforceWritePermissionInner(rootUri, getCallingPackage(), getCallingAttributionTag(), 1106 null); 1107 1108 final String rootId = DocumentsContract.getRootId(rootUri); 1109 ejectRoot(rootId); 1110 1111 return out; 1112 } 1113 1114 final Uri documentUri = extraUri; 1115 final String authority = documentUri.getAuthority(); 1116 final String documentId = DocumentsContract.getDocumentId(documentUri); 1117 1118 if (!mAuthority.equals(authority)) { 1119 throw new SecurityException( 1120 "Requested authority " + authority + " doesn't match provider " + mAuthority); 1121 } 1122 1123 if (METHOD_IS_CHILD_DOCUMENT.equals(method)) { 1124 enforceReadPermissionInner(documentUri, getCallingPackage(), 1125 getCallingAttributionTag(), null); 1126 1127 final Uri childUri = extraTargetUri; 1128 final String childAuthority = childUri.getAuthority(); 1129 final String childId = DocumentsContract.getDocumentId(childUri); 1130 1131 out.putBoolean( 1132 DocumentsContract.EXTRA_RESULT, 1133 mAuthority.equals(childAuthority) 1134 && isChildDocument(documentId, childId)); 1135 1136 } else if (METHOD_CREATE_DOCUMENT.equals(method)) { 1137 enforceWritePermissionInner(documentUri, getCallingPackage(), 1138 getCallingAttributionTag(), null); 1139 1140 final String mimeType = extras.getString(Document.COLUMN_MIME_TYPE); 1141 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 1142 final String newDocumentId = createDocument(documentId, mimeType, displayName); 1143 1144 // No need to issue new grants here, since caller either has 1145 // manage permission or a prefix grant. We might generate a 1146 // tree style URI if that's how they called us. 1147 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 1148 newDocumentId); 1149 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 1150 1151 } else if (METHOD_CREATE_WEB_LINK_INTENT.equals(method)) { 1152 enforceWritePermissionInner(documentUri, getCallingPackage(), 1153 getCallingAttributionTag(), null); 1154 1155 final Bundle options = extras.getBundle(DocumentsContract.EXTRA_OPTIONS); 1156 final IntentSender intentSender = createWebLinkIntent(documentId, options); 1157 1158 out.putParcelable(DocumentsContract.EXTRA_RESULT, intentSender); 1159 1160 } else if (METHOD_RENAME_DOCUMENT.equals(method)) { 1161 enforceWritePermissionInner(documentUri, getCallingPackage(), 1162 getCallingAttributionTag(), null); 1163 1164 final String displayName = extras.getString(Document.COLUMN_DISPLAY_NAME); 1165 final String newDocumentId = renameDocument(documentId, displayName); 1166 1167 if (newDocumentId != null) { 1168 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 1169 newDocumentId); 1170 1171 // If caller came in with a narrow grant, issue them a 1172 // narrow grant for the newly renamed document. 1173 if (!isTreeUri(newDocumentUri)) { 1174 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, 1175 documentUri); 1176 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); 1177 } 1178 1179 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 1180 1181 // Original document no longer exists, clean up any grants. 1182 revokeDocumentPermission(documentId); 1183 } 1184 1185 } else if (METHOD_DELETE_DOCUMENT.equals(method)) { 1186 enforceWritePermissionInner(documentUri, getCallingPackage(), 1187 getCallingAttributionTag(), null); 1188 deleteDocument(documentId); 1189 1190 // Document no longer exists, clean up any grants. 1191 revokeDocumentPermission(documentId); 1192 1193 } else if (METHOD_COPY_DOCUMENT.equals(method)) { 1194 final Uri targetUri = extraTargetUri; 1195 final String targetId = DocumentsContract.getDocumentId(targetUri); 1196 1197 enforceReadPermissionInner(documentUri, getCallingPackage(), 1198 getCallingAttributionTag(), null); 1199 enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingAttributionTag(), 1200 null); 1201 1202 final String newDocumentId = copyDocument(documentId, targetId); 1203 1204 if (newDocumentId != null) { 1205 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 1206 newDocumentId); 1207 1208 if (!isTreeUri(newDocumentUri)) { 1209 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, 1210 documentUri); 1211 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); 1212 } 1213 1214 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 1215 } 1216 1217 } else if (METHOD_MOVE_DOCUMENT.equals(method)) { 1218 final Uri parentSourceUri = extraParentUri; 1219 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); 1220 final Uri targetUri = extraTargetUri; 1221 final String targetId = DocumentsContract.getDocumentId(targetUri); 1222 1223 enforceWritePermissionInner(documentUri, getCallingPackage(), 1224 getCallingAttributionTag(), null); 1225 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), 1226 getCallingAttributionTag(), null); 1227 enforceWritePermissionInner(targetUri, getCallingPackage(), getCallingAttributionTag(), 1228 null); 1229 1230 final String newDocumentId = moveDocument(documentId, parentSourceId, targetId); 1231 1232 if (newDocumentId != null) { 1233 final Uri newDocumentUri = buildDocumentUriMaybeUsingTree(documentUri, 1234 newDocumentId); 1235 1236 if (!isTreeUri(newDocumentUri)) { 1237 final int modeFlags = getCallingOrSelfUriPermissionModeFlags(context, 1238 documentUri); 1239 context.grantUriPermission(getCallingPackage(), newDocumentUri, modeFlags); 1240 } 1241 1242 out.putParcelable(DocumentsContract.EXTRA_URI, newDocumentUri); 1243 } 1244 1245 } else if (METHOD_REMOVE_DOCUMENT.equals(method)) { 1246 final Uri parentSourceUri = extraParentUri; 1247 final String parentSourceId = DocumentsContract.getDocumentId(parentSourceUri); 1248 1249 enforceReadPermissionInner(parentSourceUri, getCallingPackage(), 1250 getCallingAttributionTag(), null); 1251 enforceWritePermissionInner(documentUri, getCallingPackage(), 1252 getCallingAttributionTag(), null); 1253 removeDocument(documentId, parentSourceId); 1254 1255 // It's responsibility of the provider to revoke any grants, as the document may be 1256 // still attached to another parents. 1257 } else if (METHOD_FIND_DOCUMENT_PATH.equals(method)) { 1258 final boolean isTreeUri = isTreeUri(documentUri); 1259 1260 if (isTreeUri) { 1261 enforceReadPermissionInner(documentUri, getCallingPackage(), 1262 getCallingAttributionTag(), null); 1263 } else { 1264 getContext().enforceCallingPermission(Manifest.permission.MANAGE_DOCUMENTS, null); 1265 } 1266 1267 final String parentDocumentId = isTreeUri 1268 ? DocumentsContract.getTreeDocumentId(documentUri) 1269 : null; 1270 1271 Path path = findDocumentPath(parentDocumentId, documentId); 1272 1273 // Ensure provider doesn't leak information to unprivileged callers. 1274 if (isTreeUri) { 1275 if (!Objects.equals(path.getPath().get(0), parentDocumentId)) { 1276 Log.wtf(TAG, "Provider doesn't return path from the tree root. Expected: " 1277 + parentDocumentId + " found: " + path.getPath().get(0)); 1278 1279 LinkedList<String> docs = new LinkedList<>(path.getPath()); 1280 while (docs.size() > 1 && !Objects.equals(docs.getFirst(), parentDocumentId)) { 1281 docs.removeFirst(); 1282 } 1283 path = new Path(null, docs); 1284 } 1285 1286 if (path.getRootId() != null) { 1287 Log.wtf(TAG, "Provider returns root id :" 1288 + path.getRootId() + " unexpectedly. Erase root id."); 1289 path = new Path(null, path.getPath()); 1290 } 1291 } 1292 1293 out.putParcelable(DocumentsContract.EXTRA_RESULT, path); 1294 } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) { 1295 return getDocumentMetadata(documentId); 1296 } else { 1297 throw new UnsupportedOperationException("Method not supported " + method); 1298 } 1299 1300 return out; 1301 } 1302 1303 /** 1304 * Revoke any active permission grants for the given 1305 * {@link Document#COLUMN_DOCUMENT_ID}, usually called when a document 1306 * becomes invalid. Follows the same semantics as 1307 * {@link Context#revokeUriPermission(Uri, int)}. 1308 */ revokeDocumentPermission(String documentId)1309 public final void revokeDocumentPermission(String documentId) { 1310 final Context context = getContext(); 1311 context.revokeUriPermission(buildDocumentUri(mAuthority, documentId), ~0); 1312 context.revokeUriPermission(buildTreeDocumentUri(mAuthority, documentId), ~0); 1313 } 1314 1315 /** 1316 * Implementation is provided by the parent class. Cannot be overridden. 1317 * 1318 * @see #openDocument(String, String, CancellationSignal) 1319 */ 1320 @Override openFile(Uri uri, String mode)1321 public final ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { 1322 enforceTree(uri); 1323 return openDocument(getDocumentId(uri), mode, null); 1324 } 1325 1326 /** 1327 * Implementation is provided by the parent class. Cannot be overridden. 1328 * 1329 * @see #openDocument(String, String, CancellationSignal) 1330 */ 1331 @Override openFile(Uri uri, String mode, CancellationSignal signal)1332 public final ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) 1333 throws FileNotFoundException { 1334 enforceTree(uri); 1335 return openDocument(getDocumentId(uri), mode, signal); 1336 } 1337 1338 /** 1339 * Implementation is provided by the parent class. Cannot be overridden. 1340 * 1341 * @see #openDocument(String, String, CancellationSignal) 1342 */ 1343 @Override 1344 @SuppressWarnings("resource") openAssetFile(Uri uri, String mode)1345 public final AssetFileDescriptor openAssetFile(Uri uri, String mode) 1346 throws FileNotFoundException { 1347 enforceTree(uri); 1348 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, null); 1349 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 1350 } 1351 1352 /** 1353 * Implementation is provided by the parent class. Cannot be overridden. 1354 * 1355 * @see #openDocument(String, String, CancellationSignal) 1356 */ 1357 @Override 1358 @SuppressWarnings("resource") openAssetFile(Uri uri, String mode, CancellationSignal signal)1359 public final AssetFileDescriptor openAssetFile(Uri uri, String mode, CancellationSignal signal) 1360 throws FileNotFoundException { 1361 enforceTree(uri); 1362 final ParcelFileDescriptor fd = openDocument(getDocumentId(uri), mode, signal); 1363 return fd != null ? new AssetFileDescriptor(fd, 0, -1) : null; 1364 } 1365 1366 /** 1367 * Implementation is provided by the parent class. Cannot be overridden. 1368 * 1369 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 1370 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1371 * @see #getDocumentStreamTypes(String, String) 1372 */ 1373 @Override openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts)1374 public final AssetFileDescriptor openTypedAssetFile(Uri uri, String mimeTypeFilter, Bundle opts) 1375 throws FileNotFoundException { 1376 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, null); 1377 } 1378 1379 /** 1380 * Implementation is provided by the parent class. Cannot be overridden. 1381 * 1382 * @see #openDocumentThumbnail(String, Point, CancellationSignal) 1383 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1384 * @see #getDocumentStreamTypes(String, String) 1385 */ 1386 @Override openTypedAssetFile( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)1387 public final AssetFileDescriptor openTypedAssetFile( 1388 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 1389 throws FileNotFoundException { 1390 return openTypedAssetFileImpl(uri, mimeTypeFilter, opts, signal); 1391 } 1392 1393 /** 1394 * Return a list of streamable MIME types matching the filter, which can be passed to 1395 * {@link #openTypedDocument(String, String, Bundle, CancellationSignal)}. 1396 * 1397 * <p>The default implementation returns a MIME type provided by 1398 * {@link #queryDocument(String, String[])} as long as it matches the filter and the document 1399 * does not have the {@link Document#FLAG_VIRTUAL_DOCUMENT} flag set. 1400 * 1401 * <p>Virtual documents must have at least one streamable format. 1402 * 1403 * @see #getStreamTypes(Uri, String) 1404 * @see #openTypedDocument(String, String, Bundle, CancellationSignal) 1405 */ getDocumentStreamTypes(String documentId, String mimeTypeFilter)1406 public String[] getDocumentStreamTypes(String documentId, String mimeTypeFilter) { 1407 Cursor cursor = null; 1408 try { 1409 cursor = queryDocument(documentId, null); 1410 if (cursor.moveToFirst()) { 1411 final String mimeType = 1412 cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)); 1413 final long flags = 1414 cursor.getLong(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS)); 1415 if ((flags & Document.FLAG_VIRTUAL_DOCUMENT) == 0 && mimeType != null && 1416 MimeTypeFilter.matches(mimeType, mimeTypeFilter)) { 1417 return new String[] { mimeType }; 1418 } 1419 } 1420 } catch (FileNotFoundException e) { 1421 return null; 1422 } finally { 1423 IoUtils.closeQuietly(cursor); 1424 } 1425 1426 // No streamable MIME types. 1427 return null; 1428 } 1429 1430 /** 1431 * Called by a client to determine the types of data streams that this content provider 1432 * support for the given URI. 1433 * 1434 * <p>Overriding this method is deprecated. Override {@link #openTypedDocument} instead. 1435 * 1436 * @see #getDocumentStreamTypes(String, String) 1437 */ 1438 @Override getStreamTypes(Uri uri, String mimeTypeFilter)1439 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) { 1440 enforceTree(uri); 1441 return getDocumentStreamTypes(getDocumentId(uri), mimeTypeFilter); 1442 } 1443 1444 /** 1445 * @hide 1446 */ openTypedAssetFileImpl( Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal)1447 private final AssetFileDescriptor openTypedAssetFileImpl( 1448 Uri uri, String mimeTypeFilter, Bundle opts, CancellationSignal signal) 1449 throws FileNotFoundException { 1450 enforceTree(uri); 1451 final String documentId = getDocumentId(uri); 1452 if (opts != null && opts.containsKey(ContentResolver.EXTRA_SIZE)) { 1453 final Point sizeHint = opts.getParcelable(ContentResolver.EXTRA_SIZE); 1454 return openDocumentThumbnail(documentId, sizeHint, signal); 1455 } 1456 if ("*/*".equals(mimeTypeFilter)) { 1457 // If they can take anything, the untyped open call is good enough. 1458 return openAssetFile(uri, "r"); 1459 } 1460 final String baseType = getType(uri); 1461 if (baseType != null && ClipDescription.compareMimeTypes(baseType, mimeTypeFilter)) { 1462 // Use old untyped open call if this provider has a type for this 1463 // URI and it matches the request. 1464 return openAssetFile(uri, "r"); 1465 } 1466 // For any other yet unhandled case, let the provider subclass handle it. 1467 return openTypedDocument(documentId, mimeTypeFilter, opts, signal); 1468 } 1469 } 1470