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