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