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