DocumentsContract.java revision 59d577a518333f4b4514315b6d10e8dba160abcd
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.net.TrafficStats.KB_IN_BYTES; 20import static android.system.OsConstants.SEEK_SET; 21 22import android.content.ContentProviderClient; 23import android.content.ContentResolver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.pm.ResolveInfo; 27import android.content.res.AssetFileDescriptor; 28import android.database.Cursor; 29import android.graphics.Bitmap; 30import android.graphics.BitmapFactory; 31import android.graphics.Matrix; 32import android.graphics.Point; 33import android.media.ExifInterface; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.CancellationSignal; 37import android.os.OperationCanceledException; 38import android.os.ParcelFileDescriptor; 39import android.os.ParcelFileDescriptor.OnCloseListener; 40import android.os.RemoteException; 41import android.system.ErrnoException; 42import android.system.Os; 43import android.util.Log; 44 45import libcore.io.IoUtils; 46 47import java.io.BufferedInputStream; 48import java.io.File; 49import java.io.FileDescriptor; 50import java.io.FileInputStream; 51import java.io.FileNotFoundException; 52import java.io.IOException; 53import java.util.List; 54 55/** 56 * Defines the contract between a documents provider and the platform. 57 * <p> 58 * To create a document provider, extend {@link DocumentsProvider}, which 59 * provides a foundational implementation of this contract. 60 * <p> 61 * All client apps must hold a valid URI permission grant to access documents, 62 * typically issued when a user makes a selection through 63 * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT}, 64 * or {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. 65 * 66 * @see DocumentsProvider 67 */ 68public final class DocumentsContract { 69 private static final String TAG = "Documents"; 70 71 // content://com.example/root/ 72 // content://com.example/root/sdcard/ 73 // content://com.example/root/sdcard/recent/ 74 // content://com.example/root/sdcard/search/?query=pony 75 // content://com.example/document/12/ 76 // content://com.example/document/12/children/ 77 // content://com.example/tree/12/document/24/ 78 // content://com.example/tree/12/document/24/children/ 79 80 private DocumentsContract() { 81 } 82 83 /** 84 * Intent action used to identify {@link DocumentsProvider} instances. This 85 * is used in the {@code <intent-filter>} of a {@code <provider>}. 86 */ 87 public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; 88 89 /** {@hide} */ 90 public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; 91 92 /** {@hide} */ 93 public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED"; 94 95 /** 96 * Included in {@link AssetFileDescriptor#getExtras()} when returned 97 * thumbnail should be rotated. 98 * 99 * @see MediaStore.Images.ImageColumns#ORIENTATION 100 * @hide 101 */ 102 public static final String EXTRA_ORIENTATION = "android.content.extra.ORIENTATION"; 103 104 /** {@hide} */ 105 public static final String ACTION_MANAGE_ROOT = "android.provider.action.MANAGE_ROOT"; 106 /** {@hide} */ 107 public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; 108 109 /** {@hide} */ 110 public static final String ACTION_BROWSE_ROOT = "android.provider.action.BROWSE_ROOT"; 111 112 /** 113 * Buffer is large enough to rewind past any EXIF headers. 114 */ 115 private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES); 116 117 /** 118 * Constants related to a document, including {@link Cursor} column names 119 * and flags. 120 * <p> 121 * A document can be either an openable stream (with a specific MIME type), 122 * or a directory containing additional documents (with the 123 * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a 124 * subtree containing zero or more documents, which can recursively contain 125 * even more documents and directories. 126 * <p> 127 * All columns are <em>read-only</em> to client applications. 128 */ 129 public final static class Document { 130 private Document() { 131 } 132 133 /** 134 * Unique ID of a document. This ID is both provided by and interpreted 135 * by a {@link DocumentsProvider}, and should be treated as an opaque 136 * value by client applications. This column is required. 137 * <p> 138 * Each document must have a unique ID within a provider, but that 139 * single document may be included as a child of multiple directories. 140 * <p> 141 * A provider must always return durable IDs, since they will be used to 142 * issue long-term URI permission grants when an application interacts 143 * with {@link Intent#ACTION_OPEN_DOCUMENT} and 144 * {@link Intent#ACTION_CREATE_DOCUMENT}. 145 * <p> 146 * Type: STRING 147 */ 148 public static final String COLUMN_DOCUMENT_ID = "document_id"; 149 150 /** 151 * Concrete MIME type of a document. For example, "image/png" or 152 * "application/pdf" for openable files. A document can also be a 153 * directory containing additional documents, which is represented with 154 * the {@link #MIME_TYPE_DIR} MIME type. This column is required. 155 * <p> 156 * Type: STRING 157 * 158 * @see #MIME_TYPE_DIR 159 */ 160 public static final String COLUMN_MIME_TYPE = "mime_type"; 161 162 /** 163 * Display name of a document, used as the primary title displayed to a 164 * user. This column is required. 165 * <p> 166 * Type: STRING 167 */ 168 public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; 169 170 /** 171 * Summary of a document, which may be shown to a user. This column is 172 * optional, and may be {@code null}. 173 * <p> 174 * Type: STRING 175 */ 176 public static final String COLUMN_SUMMARY = "summary"; 177 178 /** 179 * Timestamp when a document was last modified, in milliseconds since 180 * January 1, 1970 00:00:00.0 UTC. This column is required, and may be 181 * {@code null} if unknown. A {@link DocumentsProvider} can update this 182 * field using events from {@link OnCloseListener} or other reliable 183 * {@link ParcelFileDescriptor} transports. 184 * <p> 185 * Type: INTEGER (long) 186 * 187 * @see System#currentTimeMillis() 188 */ 189 public static final String COLUMN_LAST_MODIFIED = "last_modified"; 190 191 /** 192 * Specific icon resource ID for a document. This column is optional, 193 * and may be {@code null} to use a platform-provided default icon based 194 * on {@link #COLUMN_MIME_TYPE}. 195 * <p> 196 * Type: INTEGER (int) 197 */ 198 public static final String COLUMN_ICON = "icon"; 199 200 /** 201 * Flags that apply to a document. This column is required. 202 * <p> 203 * Type: INTEGER (int) 204 * 205 * @see #FLAG_SUPPORTS_WRITE 206 * @see #FLAG_SUPPORTS_DELETE 207 * @see #FLAG_SUPPORTS_THUMBNAIL 208 * @see #FLAG_DIR_PREFERS_GRID 209 * @see #FLAG_DIR_PREFERS_LAST_MODIFIED 210 */ 211 public static final String COLUMN_FLAGS = "flags"; 212 213 /** 214 * Size of a document, in bytes, or {@code null} if unknown. This column 215 * is required. 216 * <p> 217 * Type: INTEGER (long) 218 */ 219 public static final String COLUMN_SIZE = OpenableColumns.SIZE; 220 221 /** 222 * MIME type of a document which is a directory that may contain 223 * additional documents. 224 * 225 * @see #COLUMN_MIME_TYPE 226 */ 227 public static final String MIME_TYPE_DIR = "vnd.android.document/directory"; 228 229 /** 230 * Flag indicating that a document can be represented as a thumbnail. 231 * 232 * @see #COLUMN_FLAGS 233 * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri, 234 * Point, CancellationSignal) 235 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 236 * android.os.CancellationSignal) 237 */ 238 public static final int FLAG_SUPPORTS_THUMBNAIL = 1; 239 240 /** 241 * Flag indicating that a document supports writing. 242 * <p> 243 * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT}, 244 * the calling application is granted both 245 * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and 246 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual 247 * writability of a document may change over time, for example due to 248 * remote access changes. This flag indicates that a document client can 249 * expect {@link ContentResolver#openOutputStream(Uri)} to succeed. 250 * 251 * @see #COLUMN_FLAGS 252 */ 253 public static final int FLAG_SUPPORTS_WRITE = 1 << 1; 254 255 /** 256 * Flag indicating that a document is deletable. 257 * 258 * @see #COLUMN_FLAGS 259 * @see DocumentsContract#deleteDocument(ContentResolver, Uri) 260 * @see DocumentsProvider#deleteDocument(String) 261 */ 262 public static final int FLAG_SUPPORTS_DELETE = 1 << 2; 263 264 /** 265 * Flag indicating that a document is a directory that supports creation 266 * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is 267 * {@link #MIME_TYPE_DIR}. 268 * 269 * @see #COLUMN_FLAGS 270 * @see DocumentsProvider#createDocument(String, String, String) 271 */ 272 public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3; 273 274 /** 275 * Flag indicating that a directory prefers its contents be shown in a 276 * larger format grid. Usually suitable when a directory contains mostly 277 * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is 278 * {@link #MIME_TYPE_DIR}. 279 * 280 * @see #COLUMN_FLAGS 281 */ 282 public static final int FLAG_DIR_PREFERS_GRID = 1 << 4; 283 284 /** 285 * Flag indicating that a directory prefers its contents be sorted by 286 * {@link #COLUMN_LAST_MODIFIED}. Only valid when 287 * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. 288 * 289 * @see #COLUMN_FLAGS 290 */ 291 public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; 292 293 /** 294 * Flag indicating that a document can be renamed. 295 * 296 * @see #COLUMN_FLAGS 297 * @see DocumentsContract#renameDocument(ContentProviderClient, Uri, 298 * String) 299 * @see DocumentsProvider#renameDocument(String, String) 300 */ 301 public static final int FLAG_SUPPORTS_RENAME = 1 << 6; 302 303 /** 304 * Flag indicating that document titles should be hidden when viewing 305 * this directory in a larger format grid. For example, a directory 306 * containing only images may want the image thumbnails to speak for 307 * themselves. Only valid when {@link #COLUMN_MIME_TYPE} is 308 * {@link #MIME_TYPE_DIR}. 309 * 310 * @see #COLUMN_FLAGS 311 * @see #FLAG_DIR_PREFERS_GRID 312 * @hide 313 */ 314 public static final int FLAG_DIR_HIDE_GRID_TITLES = 1 << 16; 315 } 316 317 /** 318 * Constants related to a root of documents, including {@link Cursor} column 319 * names and flags. A root is the start of a tree of documents, such as a 320 * physical storage device, or an account. Each root starts at the directory 321 * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively 322 * contain both documents and directories. 323 * <p> 324 * All columns are <em>read-only</em> to client applications. 325 */ 326 public final static class Root { 327 private Root() { 328 } 329 330 /** 331 * Unique ID of a root. This ID is both provided by and interpreted by a 332 * {@link DocumentsProvider}, and should be treated as an opaque value 333 * by client applications. This column is required. 334 * <p> 335 * Type: STRING 336 */ 337 public static final String COLUMN_ROOT_ID = "root_id"; 338 339 /** 340 * Flags that apply to a root. This column is required. 341 * <p> 342 * Type: INTEGER (int) 343 * 344 * @see #FLAG_LOCAL_ONLY 345 * @see #FLAG_SUPPORTS_CREATE 346 * @see #FLAG_SUPPORTS_RECENTS 347 * @see #FLAG_SUPPORTS_SEARCH 348 */ 349 public static final String COLUMN_FLAGS = "flags"; 350 351 /** 352 * Icon resource ID for a root. This column is required. 353 * <p> 354 * Type: INTEGER (int) 355 */ 356 public static final String COLUMN_ICON = "icon"; 357 358 /** 359 * Title for a root, which will be shown to a user. This column is 360 * required. For a single storage service surfacing multiple accounts as 361 * different roots, this title should be the name of the service. 362 * <p> 363 * Type: STRING 364 */ 365 public static final String COLUMN_TITLE = "title"; 366 367 /** 368 * Summary for this root, which may be shown to a user. This column is 369 * optional, and may be {@code null}. For a single storage service 370 * surfacing multiple accounts as different roots, this summary should 371 * be the name of the account. 372 * <p> 373 * Type: STRING 374 */ 375 public static final String COLUMN_SUMMARY = "summary"; 376 377 /** 378 * Document which is a directory that represents the top directory of 379 * this root. This column is required. 380 * <p> 381 * Type: STRING 382 * 383 * @see Document#COLUMN_DOCUMENT_ID 384 */ 385 public static final String COLUMN_DOCUMENT_ID = "document_id"; 386 387 /** 388 * Number of bytes available in this root. This column is optional, and 389 * may be {@code null} if unknown or unbounded. 390 * <p> 391 * Type: INTEGER (long) 392 */ 393 public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; 394 395 /** 396 * MIME types supported by this root. This column is optional, and if 397 * {@code null} the root is assumed to support all MIME types. Multiple 398 * MIME types can be separated by a newline. For example, a root 399 * supporting audio might return "audio/*\napplication/x-flac". 400 * <p> 401 * Type: STRING 402 */ 403 public static final String COLUMN_MIME_TYPES = "mime_types"; 404 405 /** {@hide} */ 406 public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; 407 408 /** 409 * Flag indicating that at least one directory under this root supports 410 * creating content. Roots with this flag will be shown when an 411 * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}. 412 * 413 * @see #COLUMN_FLAGS 414 */ 415 public static final int FLAG_SUPPORTS_CREATE = 1; 416 417 /** 418 * Flag indicating that this root offers content that is strictly local 419 * on the device. That is, no network requests are made for the content. 420 * 421 * @see #COLUMN_FLAGS 422 * @see Intent#EXTRA_LOCAL_ONLY 423 */ 424 public static final int FLAG_LOCAL_ONLY = 1 << 1; 425 426 /** 427 * Flag indicating that this root can be queried to provide recently 428 * modified documents. 429 * 430 * @see #COLUMN_FLAGS 431 * @see DocumentsContract#buildRecentDocumentsUri(String, String) 432 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 433 */ 434 public static final int FLAG_SUPPORTS_RECENTS = 1 << 2; 435 436 /** 437 * Flag indicating that this root supports search. 438 * 439 * @see #COLUMN_FLAGS 440 * @see DocumentsContract#buildSearchDocumentsUri(String, String, 441 * String) 442 * @see DocumentsProvider#querySearchDocuments(String, String, 443 * String[]) 444 */ 445 public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; 446 447 /** 448 * Flag indicating that this root supports testing parent child 449 * relationships. 450 * 451 * @see #COLUMN_FLAGS 452 * @see DocumentsProvider#isChildDocument(String, String) 453 */ 454 public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4; 455 456 /** 457 * Flag indicating that this root is currently empty. This may be used 458 * to hide the root when opening documents, but the root will still be 459 * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is 460 * also set. If the value of this flag changes, such as when a root 461 * becomes non-empty, you must send a content changed notification for 462 * {@link DocumentsContract#buildRootsUri(String)}. 463 * 464 * @see #COLUMN_FLAGS 465 * @see ContentResolver#notifyChange(Uri, 466 * android.database.ContentObserver, boolean) 467 * @hide 468 */ 469 public static final int FLAG_EMPTY = 1 << 16; 470 471 /** 472 * Flag indicating that this root should only be visible to advanced 473 * users. 474 * 475 * @see #COLUMN_FLAGS 476 * @hide 477 */ 478 public static final int FLAG_ADVANCED = 1 << 17; 479 } 480 481 /** 482 * Optional boolean flag included in a directory {@link Cursor#getExtras()} 483 * indicating that a document provider is still loading data. For example, a 484 * provider has returned some results, but is still waiting on an 485 * outstanding network request. The provider must send a content changed 486 * notification when loading is finished. 487 * 488 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, 489 * boolean) 490 */ 491 public static final String EXTRA_LOADING = "loading"; 492 493 /** 494 * Optional string included in a directory {@link Cursor#getExtras()} 495 * providing an informational message that should be shown to a user. For 496 * example, a provider may wish to indicate that not all documents are 497 * available. 498 */ 499 public static final String EXTRA_INFO = "info"; 500 501 /** 502 * Optional string included in a directory {@link Cursor#getExtras()} 503 * providing an error message that should be shown to a user. For example, a 504 * provider may wish to indicate that a network error occurred. The user may 505 * choose to retry, resulting in a new query. 506 */ 507 public static final String EXTRA_ERROR = "error"; 508 509 /** {@hide} */ 510 public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; 511 /** {@hide} */ 512 public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; 513 /** {@hide} */ 514 public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; 515 516 /** {@hide} */ 517 public static final String EXTRA_URI = "uri"; 518 519 private static final String PATH_ROOT = "root"; 520 private static final String PATH_RECENT = "recent"; 521 private static final String PATH_DOCUMENT = "document"; 522 private static final String PATH_CHILDREN = "children"; 523 private static final String PATH_SEARCH = "search"; 524 private static final String PATH_TREE = "tree"; 525 526 private static final String PARAM_QUERY = "query"; 527 private static final String PARAM_MANAGE = "manage"; 528 529 /** 530 * Build URI representing the roots of a document provider. When queried, a 531 * provider will return one or more rows with columns defined by 532 * {@link Root}. 533 * 534 * @see DocumentsProvider#queryRoots(String[]) 535 */ 536 public static Uri buildRootsUri(String authority) { 537 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 538 .authority(authority).appendPath(PATH_ROOT).build(); 539 } 540 541 /** 542 * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a 543 * document provider. 544 * 545 * @see #getRootId(Uri) 546 */ 547 public static Uri buildRootUri(String authority, String rootId) { 548 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 549 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); 550 } 551 552 /** 553 * Build URI representing the recently modified documents of a specific root 554 * in a document provider. When queried, a provider will return zero or more 555 * rows with columns defined by {@link Document}. 556 * 557 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 558 * @see #getRootId(Uri) 559 */ 560 public static Uri buildRecentDocumentsUri(String authority, String rootId) { 561 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 562 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) 563 .appendPath(PATH_RECENT).build(); 564 } 565 566 /** 567 * Build URI representing access to descendant documents of the given 568 * {@link Document#COLUMN_DOCUMENT_ID}. 569 * 570 * @see #getTreeDocumentId(Uri) 571 */ 572 public static Uri buildTreeDocumentUri(String authority, String documentId) { 573 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 574 .appendPath(PATH_TREE).appendPath(documentId).build(); 575 } 576 577 /** 578 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in 579 * a document provider. When queried, a provider will return a single row 580 * with columns defined by {@link Document}. 581 * 582 * @see DocumentsProvider#queryDocument(String, String[]) 583 * @see #getDocumentId(Uri) 584 */ 585 public static Uri buildDocumentUri(String authority, String documentId) { 586 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 587 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); 588 } 589 590 /** 591 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in 592 * a document provider. When queried, a provider will return a single row 593 * with columns defined by {@link Document}. 594 * <p> 595 * However, instead of directly accessing the target document, the returned 596 * URI will leverage access granted through a subtree URI, typically 597 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document 598 * must be a descendant (child, grandchild, etc) of the subtree. 599 * <p> 600 * This is typically used to access documents under a user-selected 601 * directory tree, since it doesn't require the user to separately confirm 602 * each new document access. 603 * 604 * @param treeUri the subtree to leverage to gain access to the target 605 * document. The target directory must be a descendant of this 606 * subtree. 607 * @param documentId the target document, which the caller may not have 608 * direct access to. 609 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 610 * @see DocumentsProvider#isChildDocument(String, String) 611 * @see #buildDocumentUri(String, String) 612 */ 613 public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) { 614 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 615 .authority(treeUri.getAuthority()).appendPath(PATH_TREE) 616 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) 617 .appendPath(documentId).build(); 618 } 619 620 /** {@hide} */ 621 public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) { 622 if (isTreeUri(baseUri)) { 623 return buildDocumentUriUsingTree(baseUri, documentId); 624 } else { 625 return buildDocumentUri(baseUri.getAuthority(), documentId); 626 } 627 } 628 629 /** 630 * Build URI representing the children of the target directory in a document 631 * provider. When queried, a provider will return zero or more rows with 632 * columns defined by {@link Document}. 633 * 634 * @param parentDocumentId the document to return children for, which must 635 * be a directory with MIME type of 636 * {@link Document#MIME_TYPE_DIR}. 637 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 638 * @see #getDocumentId(Uri) 639 */ 640 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { 641 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 642 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) 643 .build(); 644 } 645 646 /** 647 * Build URI representing the children of the target directory in a document 648 * provider. When queried, a provider will return zero or more rows with 649 * columns defined by {@link Document}. 650 * <p> 651 * However, instead of directly accessing the target directory, the returned 652 * URI will leverage access granted through a subtree URI, typically 653 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target 654 * directory must be a descendant (child, grandchild, etc) of the subtree. 655 * <p> 656 * This is typically used to access documents under a user-selected 657 * directory tree, since it doesn't require the user to separately confirm 658 * each new document access. 659 * 660 * @param treeUri the subtree to leverage to gain access to the target 661 * document. The target directory must be a descendant of this 662 * subtree. 663 * @param parentDocumentId the document to return children for, which the 664 * caller may not have direct access to, and which must be a 665 * directory with MIME type of {@link Document#MIME_TYPE_DIR}. 666 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 667 * @see DocumentsProvider#isChildDocument(String, String) 668 * @see #buildChildDocumentsUri(String, String) 669 */ 670 public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) { 671 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 672 .authority(treeUri.getAuthority()).appendPath(PATH_TREE) 673 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) 674 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build(); 675 } 676 677 /** 678 * Build URI representing a search for matching documents under a specific 679 * root in a document provider. When queried, a provider will return zero or 680 * more rows with columns defined by {@link Document}. 681 * 682 * @see DocumentsProvider#querySearchDocuments(String, String, String[]) 683 * @see #getRootId(Uri) 684 * @see #getSearchDocumentsQuery(Uri) 685 */ 686 public static Uri buildSearchDocumentsUri( 687 String authority, String rootId, String query) { 688 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 689 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH) 690 .appendQueryParameter(PARAM_QUERY, query).build(); 691 } 692 693 /** 694 * Test if the given URI represents a {@link Document} backed by a 695 * {@link DocumentsProvider}. 696 * 697 * @see #buildDocumentUri(String, String) 698 * @see #buildDocumentUriUsingTree(Uri, String) 699 */ 700 public static boolean isDocumentUri(Context context, Uri uri) { 701 final List<String> paths = uri.getPathSegments(); 702 if (paths.size() == 2 && PATH_DOCUMENT.equals(paths.get(0))) { 703 return isDocumentsProvider(context, uri.getAuthority()); 704 } 705 if (paths.size() == 4 && PATH_TREE.equals(paths.get(0)) 706 && PATH_DOCUMENT.equals(paths.get(2))) { 707 return isDocumentsProvider(context, uri.getAuthority()); 708 } 709 return false; 710 } 711 712 /** {@hide} */ 713 public static boolean isTreeUri(Uri uri) { 714 final List<String> paths = uri.getPathSegments(); 715 return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))); 716 } 717 718 private static boolean isDocumentsProvider(Context context, String authority) { 719 final Intent intent = new Intent(PROVIDER_INTERFACE); 720 final List<ResolveInfo> infos = context.getPackageManager() 721 .queryIntentContentProviders(intent, 0); 722 for (ResolveInfo info : infos) { 723 if (authority.equals(info.providerInfo.authority)) { 724 return true; 725 } 726 } 727 return false; 728 } 729 730 /** 731 * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI. 732 */ 733 public static String getRootId(Uri rootUri) { 734 final List<String> paths = rootUri.getPathSegments(); 735 if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) { 736 return paths.get(1); 737 } 738 throw new IllegalArgumentException("Invalid URI: " + rootUri); 739 } 740 741 /** 742 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI. 743 * 744 * @see #isDocumentUri(Context, Uri) 745 */ 746 public static String getDocumentId(Uri documentUri) { 747 final List<String> paths = documentUri.getPathSegments(); 748 if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) { 749 return paths.get(1); 750 } 751 if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0)) 752 && PATH_DOCUMENT.equals(paths.get(2))) { 753 return paths.get(3); 754 } 755 throw new IllegalArgumentException("Invalid URI: " + documentUri); 756 } 757 758 /** 759 * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI. 760 */ 761 public static String getTreeDocumentId(Uri documentUri) { 762 final List<String> paths = documentUri.getPathSegments(); 763 if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) { 764 return paths.get(1); 765 } 766 throw new IllegalArgumentException("Invalid URI: " + documentUri); 767 } 768 769 /** 770 * Extract the search query from a URI built by 771 * {@link #buildSearchDocumentsUri(String, String, String)}. 772 */ 773 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { 774 return searchDocumentsUri.getQueryParameter(PARAM_QUERY); 775 } 776 777 /** {@hide} */ 778 public static Uri setManageMode(Uri uri) { 779 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); 780 } 781 782 /** {@hide} */ 783 public static boolean isManageMode(Uri uri) { 784 return uri.getBooleanQueryParameter(PARAM_MANAGE, false); 785 } 786 787 /** 788 * Return thumbnail representing the document at the given URI. Callers are 789 * responsible for their own in-memory caching. 790 * 791 * @param documentUri document to return thumbnail for, which must have 792 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. 793 * @param size optimal thumbnail size desired. A provider may return a 794 * thumbnail of a different size, but never more than double the 795 * requested size. 796 * @param signal signal used to indicate if caller is no longer interested 797 * in the thumbnail. 798 * @return decoded thumbnail, or {@code null} if problem was encountered. 799 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 800 * android.os.CancellationSignal) 801 */ 802 public static Bitmap getDocumentThumbnail( 803 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 804 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 805 documentUri.getAuthority()); 806 try { 807 return getDocumentThumbnail(client, documentUri, size, signal); 808 } catch (Exception e) { 809 if (!(e instanceof OperationCanceledException)) { 810 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 811 } 812 return null; 813 } finally { 814 ContentProviderClient.releaseQuietly(client); 815 } 816 } 817 818 /** {@hide} */ 819 public static Bitmap getDocumentThumbnail( 820 ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) 821 throws RemoteException, IOException { 822 final Bundle openOpts = new Bundle(); 823 openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size); 824 825 AssetFileDescriptor afd = null; 826 Bitmap bitmap = null; 827 try { 828 afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); 829 830 final FileDescriptor fd = afd.getFileDescriptor(); 831 final long offset = afd.getStartOffset(); 832 833 // Try seeking on the returned FD, since it gives us the most 834 // optimal decode path; otherwise fall back to buffering. 835 BufferedInputStream is = null; 836 try { 837 Os.lseek(fd, offset, SEEK_SET); 838 } catch (ErrnoException e) { 839 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); 840 is.mark(THUMBNAIL_BUFFER_SIZE); 841 } 842 843 // We requested a rough thumbnail size, but the remote size may have 844 // returned something giant, so defensively scale down as needed. 845 final BitmapFactory.Options opts = new BitmapFactory.Options(); 846 opts.inJustDecodeBounds = true; 847 if (is != null) { 848 BitmapFactory.decodeStream(is, null, opts); 849 } else { 850 BitmapFactory.decodeFileDescriptor(fd, null, opts); 851 } 852 853 final int widthSample = opts.outWidth / size.x; 854 final int heightSample = opts.outHeight / size.y; 855 856 opts.inJustDecodeBounds = false; 857 opts.inSampleSize = Math.min(widthSample, heightSample); 858 if (is != null) { 859 is.reset(); 860 bitmap = BitmapFactory.decodeStream(is, null, opts); 861 } else { 862 try { 863 Os.lseek(fd, offset, SEEK_SET); 864 } catch (ErrnoException e) { 865 e.rethrowAsIOException(); 866 } 867 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts); 868 } 869 870 // Transform the bitmap if requested. We use a side-channel to 871 // communicate the orientation, since EXIF thumbnails don't contain 872 // the rotation flags of the original image. 873 final Bundle extras = afd.getExtras(); 874 final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; 875 if (orientation != 0) { 876 final int width = bitmap.getWidth(); 877 final int height = bitmap.getHeight(); 878 879 final Matrix m = new Matrix(); 880 m.setRotate(orientation, width / 2, height / 2); 881 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); 882 } 883 } finally { 884 IoUtils.closeQuietly(afd); 885 } 886 887 return bitmap; 888 } 889 890 /** 891 * Create a new document with given MIME type and display name. 892 * 893 * @param parentDocumentUri directory with 894 * {@link Document#FLAG_DIR_SUPPORTS_CREATE} 895 * @param mimeType MIME type of new document 896 * @param displayName name of new document 897 * @return newly created document, or {@code null} if failed 898 */ 899 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, 900 String mimeType, String displayName) { 901 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 902 parentDocumentUri.getAuthority()); 903 try { 904 return createDocument(client, parentDocumentUri, mimeType, displayName); 905 } catch (Exception e) { 906 Log.w(TAG, "Failed to create document", e); 907 return null; 908 } finally { 909 ContentProviderClient.releaseQuietly(client); 910 } 911 } 912 913 /** {@hide} */ 914 public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, 915 String mimeType, String displayName) throws RemoteException { 916 final Bundle in = new Bundle(); 917 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); 918 in.putString(Document.COLUMN_MIME_TYPE, mimeType); 919 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 920 921 final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); 922 return out.getParcelable(DocumentsContract.EXTRA_URI); 923 } 924 925 /** 926 * Change the display name of an existing document. 927 * <p> 928 * If the underlying provider needs to create a new 929 * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display 930 * name, that new document is returned and the original document is no 931 * longer valid. Otherwise, the original document is returned. 932 * 933 * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME} 934 * @param displayName updated name for document 935 * @return the existing or new document after the rename, or {@code null} if 936 * failed. 937 */ 938 public static Uri renameDocument(ContentResolver resolver, Uri documentUri, 939 String displayName) { 940 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 941 documentUri.getAuthority()); 942 try { 943 return renameDocument(client, documentUri, displayName); 944 } catch (Exception e) { 945 Log.w(TAG, "Failed to rename document", e); 946 return null; 947 } finally { 948 ContentProviderClient.releaseQuietly(client); 949 } 950 } 951 952 /** {@hide} */ 953 public static Uri renameDocument(ContentProviderClient client, Uri documentUri, 954 String displayName) throws RemoteException { 955 final Bundle in = new Bundle(); 956 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 957 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 958 959 final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); 960 final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI); 961 return (outUri != null) ? outUri : documentUri; 962 } 963 964 /** 965 * Delete the given document. 966 * 967 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} 968 * @return if the document was deleted successfully. 969 */ 970 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 971 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 972 documentUri.getAuthority()); 973 try { 974 deleteDocument(client, documentUri); 975 return true; 976 } catch (Exception e) { 977 Log.w(TAG, "Failed to delete document", e); 978 return false; 979 } finally { 980 ContentProviderClient.releaseQuietly(client); 981 } 982 } 983 984 /** {@hide} */ 985 public static void deleteDocument(ContentProviderClient client, Uri documentUri) 986 throws RemoteException { 987 final Bundle in = new Bundle(); 988 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 989 990 client.call(METHOD_DELETE_DOCUMENT, null, in); 991 } 992 993 /** 994 * Open the given image for thumbnail purposes, using any embedded EXIF 995 * thumbnail if available, and providing orientation hints from the parent 996 * image. 997 * 998 * @hide 999 */ 1000 public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException { 1001 final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 1002 file, ParcelFileDescriptor.MODE_READ_ONLY); 1003 Bundle extras = null; 1004 1005 try { 1006 final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); 1007 1008 switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) { 1009 case ExifInterface.ORIENTATION_ROTATE_90: 1010 extras = new Bundle(1); 1011 extras.putInt(EXTRA_ORIENTATION, 90); 1012 break; 1013 case ExifInterface.ORIENTATION_ROTATE_180: 1014 extras = new Bundle(1); 1015 extras.putInt(EXTRA_ORIENTATION, 180); 1016 break; 1017 case ExifInterface.ORIENTATION_ROTATE_270: 1018 extras = new Bundle(1); 1019 extras.putInt(EXTRA_ORIENTATION, 270); 1020 break; 1021 } 1022 1023 final long[] thumb = exif.getThumbnailRange(); 1024 if (thumb != null) { 1025 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras); 1026 } 1027 } catch (IOException e) { 1028 } 1029 1030 return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); 1031 } 1032} 1033