1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.provider; 18 19 import static android.net.TrafficStats.KB_IN_BYTES; 20 import static android.system.OsConstants.SEEK_SET; 21 22 import android.annotation.Nullable; 23 import android.content.ContentProviderClient; 24 import android.content.ContentResolver; 25 import android.content.Context; 26 import android.content.Intent; 27 import android.content.pm.ResolveInfo; 28 import android.content.res.AssetFileDescriptor; 29 import android.database.Cursor; 30 import android.graphics.Bitmap; 31 import android.graphics.BitmapFactory; 32 import android.graphics.Matrix; 33 import android.graphics.Point; 34 import android.media.ExifInterface; 35 import android.net.Uri; 36 import android.os.Bundle; 37 import android.os.CancellationSignal; 38 import android.os.OperationCanceledException; 39 import android.os.ParcelFileDescriptor; 40 import android.os.ParcelFileDescriptor.OnCloseListener; 41 import android.os.RemoteException; 42 import android.os.storage.StorageVolume; 43 import android.system.ErrnoException; 44 import android.system.Os; 45 import android.util.Log; 46 47 import libcore.io.IoUtils; 48 49 import java.io.BufferedInputStream; 50 import java.io.File; 51 import java.io.FileDescriptor; 52 import java.io.FileInputStream; 53 import java.io.FileNotFoundException; 54 import java.io.IOException; 55 import java.util.List; 56 57 /** 58 * Defines the contract between a documents provider and the platform. 59 * <p> 60 * To create a document provider, extend {@link DocumentsProvider}, which 61 * provides a foundational implementation of this contract. 62 * <p> 63 * All client apps must hold a valid URI permission grant to access documents, 64 * typically issued when a user makes a selection through 65 * {@link Intent#ACTION_OPEN_DOCUMENT}, {@link Intent#ACTION_CREATE_DOCUMENT}, 66 * {@link Intent#ACTION_OPEN_DOCUMENT_TREE}, or 67 * {@link StorageVolume#createAccessIntent(String) StorageVolume.createAccessIntent}. 68 * 69 * @see DocumentsProvider 70 */ 71 public final class DocumentsContract { 72 private static final String TAG = "DocumentsContract"; 73 74 // content://com.example/root/ 75 // content://com.example/root/sdcard/ 76 // content://com.example/root/sdcard/recent/ 77 // content://com.example/root/sdcard/search/?query=pony 78 // content://com.example/document/12/ 79 // content://com.example/document/12/children/ 80 // content://com.example/tree/12/document/24/ 81 // content://com.example/tree/12/document/24/children/ 82 83 private DocumentsContract() { 84 } 85 86 /** 87 * Intent action used to identify {@link DocumentsProvider} instances. This 88 * is used in the {@code <intent-filter>} of a {@code <provider>}. 89 */ 90 public static final String PROVIDER_INTERFACE = "android.content.action.DOCUMENTS_PROVIDER"; 91 92 /** {@hide} */ 93 public static final String EXTRA_PACKAGE_NAME = "android.content.extra.PACKAGE_NAME"; 94 95 /** {@hide} */ 96 public static final String EXTRA_SHOW_ADVANCED = "android.content.extra.SHOW_ADVANCED"; 97 98 /** {@hide} */ 99 public static final String EXTRA_SHOW_FILESIZE = "android.content.extra.SHOW_FILESIZE"; 100 101 /** {@hide} */ 102 public static final String EXTRA_FANCY_FEATURES = "android.content.extra.FANCY"; 103 104 /** {@hide} */ 105 public static final String EXTRA_TARGET_URI = "android.content.extra.TARGET_URI"; 106 107 /** 108 * Set this in a DocumentsUI intent to cause a package's own roots to be 109 * excluded from the roots list. 110 */ 111 public static final String EXTRA_EXCLUDE_SELF = "android.provider.extra.EXCLUDE_SELF"; 112 113 /** 114 * Included in {@link AssetFileDescriptor#getExtras()} when returned 115 * thumbnail should be rotated. 116 * 117 * @see MediaStore.Images.ImageColumns#ORIENTATION 118 */ 119 public static final String EXTRA_ORIENTATION = "android.provider.extra.ORIENTATION"; 120 121 /** 122 * Overrides the default prompt text in DocumentsUI when set in an intent. 123 */ 124 public static final String EXTRA_PROMPT = "android.provider.extra.PROMPT"; 125 126 /** {@hide} */ 127 public static final String ACTION_MANAGE_DOCUMENT = "android.provider.action.MANAGE_DOCUMENT"; 128 129 /** {@hide} */ 130 public static final String ACTION_BROWSE = "android.provider.action.BROWSE"; 131 132 /** {@hide} */ 133 public static final String 134 ACTION_DOCUMENT_ROOT_SETTINGS = "android.provider.action.DOCUMENT_ROOT_SETTINGS"; 135 136 /** 137 * Buffer is large enough to rewind past any EXIF headers. 138 */ 139 private static final int THUMBNAIL_BUFFER_SIZE = (int) (128 * KB_IN_BYTES); 140 141 /** {@hide} */ 142 public static final String PACKAGE_DOCUMENTS_UI = "com.android.documentsui"; 143 144 /** 145 * Constants related to a document, including {@link Cursor} column names 146 * and flags. 147 * <p> 148 * A document can be either an openable stream (with a specific MIME type), 149 * or a directory containing additional documents (with the 150 * {@link #MIME_TYPE_DIR} MIME type). A directory represents the top of a 151 * subtree containing zero or more documents, which can recursively contain 152 * even more documents and directories. 153 * <p> 154 * All columns are <em>read-only</em> to client applications. 155 */ 156 public final static class Document { 157 private Document() { 158 } 159 160 /** 161 * Unique ID of a document. This ID is both provided by and interpreted 162 * by a {@link DocumentsProvider}, and should be treated as an opaque 163 * value by client applications. This column is required. 164 * <p> 165 * Each document must have a unique ID within a provider, but that 166 * single document may be included as a child of multiple directories. 167 * <p> 168 * A provider must always return durable IDs, since they will be used to 169 * issue long-term URI permission grants when an application interacts 170 * with {@link Intent#ACTION_OPEN_DOCUMENT} and 171 * {@link Intent#ACTION_CREATE_DOCUMENT}. 172 * <p> 173 * Type: STRING 174 */ 175 public static final String COLUMN_DOCUMENT_ID = "document_id"; 176 177 /** 178 * Concrete MIME type of a document. For example, "image/png" or 179 * "application/pdf" for openable files. A document can also be a 180 * directory containing additional documents, which is represented with 181 * the {@link #MIME_TYPE_DIR} MIME type. This column is required. 182 * <p> 183 * Type: STRING 184 * 185 * @see #MIME_TYPE_DIR 186 */ 187 public static final String COLUMN_MIME_TYPE = "mime_type"; 188 189 /** 190 * Display name of a document, used as the primary title displayed to a 191 * user. This column is required. 192 * <p> 193 * Type: STRING 194 */ 195 public static final String COLUMN_DISPLAY_NAME = OpenableColumns.DISPLAY_NAME; 196 197 /** 198 * Summary of a document, which may be shown to a user. This column is 199 * optional, and may be {@code null}. 200 * <p> 201 * Type: STRING 202 */ 203 public static final String COLUMN_SUMMARY = "summary"; 204 205 /** 206 * Timestamp when a document was last modified, in milliseconds since 207 * January 1, 1970 00:00:00.0 UTC. This column is required, and may be 208 * {@code null} if unknown. A {@link DocumentsProvider} can update this 209 * field using events from {@link OnCloseListener} or other reliable 210 * {@link ParcelFileDescriptor} transports. 211 * <p> 212 * Type: INTEGER (long) 213 * 214 * @see System#currentTimeMillis() 215 */ 216 public static final String COLUMN_LAST_MODIFIED = "last_modified"; 217 218 /** 219 * Specific icon resource ID for a document. This column is optional, 220 * and may be {@code null} to use a platform-provided default icon based 221 * on {@link #COLUMN_MIME_TYPE}. 222 * <p> 223 * Type: INTEGER (int) 224 */ 225 public static final String COLUMN_ICON = "icon"; 226 227 /** 228 * Flags that apply to a document. This column is required. 229 * <p> 230 * Type: INTEGER (int) 231 * 232 * @see #FLAG_SUPPORTS_WRITE 233 * @see #FLAG_SUPPORTS_DELETE 234 * @see #FLAG_SUPPORTS_THUMBNAIL 235 * @see #FLAG_DIR_PREFERS_GRID 236 * @see #FLAG_DIR_PREFERS_LAST_MODIFIED 237 * @see #FLAG_VIRTUAL_DOCUMENT 238 * @see #FLAG_SUPPORTS_COPY 239 * @see #FLAG_SUPPORTS_MOVE 240 * @see #FLAG_SUPPORTS_REMOVE 241 */ 242 public static final String COLUMN_FLAGS = "flags"; 243 244 /** 245 * Size of a document, in bytes, or {@code null} if unknown. This column 246 * is required. 247 * <p> 248 * Type: INTEGER (long) 249 */ 250 public static final String COLUMN_SIZE = OpenableColumns.SIZE; 251 252 /** 253 * MIME type of a document which is a directory that may contain 254 * additional documents. 255 * 256 * @see #COLUMN_MIME_TYPE 257 */ 258 public static final String MIME_TYPE_DIR = "vnd.android.document/directory"; 259 260 /** 261 * Flag indicating that a document can be represented as a thumbnail. 262 * 263 * @see #COLUMN_FLAGS 264 * @see DocumentsContract#getDocumentThumbnail(ContentResolver, Uri, 265 * Point, CancellationSignal) 266 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 267 * android.os.CancellationSignal) 268 */ 269 public static final int FLAG_SUPPORTS_THUMBNAIL = 1; 270 271 /** 272 * Flag indicating that a document supports writing. 273 * <p> 274 * When a document is opened with {@link Intent#ACTION_OPEN_DOCUMENT}, 275 * the calling application is granted both 276 * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION} and 277 * {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION}. However, the actual 278 * writability of a document may change over time, for example due to 279 * remote access changes. This flag indicates that a document client can 280 * expect {@link ContentResolver#openOutputStream(Uri)} to succeed. 281 * 282 * @see #COLUMN_FLAGS 283 */ 284 public static final int FLAG_SUPPORTS_WRITE = 1 << 1; 285 286 /** 287 * Flag indicating that a document is deletable. 288 * 289 * @see #COLUMN_FLAGS 290 * @see DocumentsContract#deleteDocument(ContentResolver, Uri) 291 * @see DocumentsProvider#deleteDocument(String) 292 */ 293 public static final int FLAG_SUPPORTS_DELETE = 1 << 2; 294 295 /** 296 * Flag indicating that a document is a directory that supports creation 297 * of new files within it. Only valid when {@link #COLUMN_MIME_TYPE} is 298 * {@link #MIME_TYPE_DIR}. 299 * 300 * @see #COLUMN_FLAGS 301 * @see DocumentsProvider#createDocument(String, String, String) 302 */ 303 public static final int FLAG_DIR_SUPPORTS_CREATE = 1 << 3; 304 305 /** 306 * Flag indicating that a directory prefers its contents be shown in a 307 * larger format grid. Usually suitable when a directory contains mostly 308 * pictures. Only valid when {@link #COLUMN_MIME_TYPE} is 309 * {@link #MIME_TYPE_DIR}. 310 * 311 * @see #COLUMN_FLAGS 312 */ 313 public static final int FLAG_DIR_PREFERS_GRID = 1 << 4; 314 315 /** 316 * Flag indicating that a directory prefers its contents be sorted by 317 * {@link #COLUMN_LAST_MODIFIED}. Only valid when 318 * {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}. 319 * 320 * @see #COLUMN_FLAGS 321 */ 322 public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 1 << 5; 323 324 /** 325 * Flag indicating that a document can be renamed. 326 * 327 * @see #COLUMN_FLAGS 328 * @see DocumentsContract#renameDocument(ContentResolver, Uri, 329 * String) 330 * @see DocumentsProvider#renameDocument(String, String) 331 */ 332 public static final int FLAG_SUPPORTS_RENAME = 1 << 6; 333 334 /** 335 * Flag indicating that a document can be copied to another location 336 * within the same document provider. 337 * 338 * @see #COLUMN_FLAGS 339 * @see DocumentsContract#copyDocument(ContentResolver, Uri, Uri) 340 * @see DocumentsProvider#copyDocument(String, String) 341 */ 342 public static final int FLAG_SUPPORTS_COPY = 1 << 7; 343 344 /** 345 * Flag indicating that a document can be moved to another location 346 * within the same document provider. 347 * 348 * @see #COLUMN_FLAGS 349 * @see DocumentsContract#moveDocument(ContentResolver, Uri, Uri, Uri) 350 * @see DocumentsProvider#moveDocument(String, String, String) 351 */ 352 public static final int FLAG_SUPPORTS_MOVE = 1 << 8; 353 354 /** 355 * Flag indicating that a document is virtual, and doesn't have byte 356 * representation in the MIME type specified as {@link #COLUMN_MIME_TYPE}. 357 * 358 * @see #COLUMN_FLAGS 359 * @see #COLUMN_MIME_TYPE 360 * @see DocumentsProvider#openTypedDocument(String, String, Bundle, 361 * android.os.CancellationSignal) 362 * @see DocumentsProvider#getDocumentStreamTypes(String, String) 363 */ 364 public static final int FLAG_VIRTUAL_DOCUMENT = 1 << 9; 365 366 /** 367 * Flag indicating that a document can be removed from a parent. 368 * 369 * @see #COLUMN_FLAGS 370 * @see DocumentsContract#removeDocument(ContentResolver, Uri, Uri) 371 * @see DocumentsProvider#removeDocument(String, String) 372 */ 373 public static final int FLAG_SUPPORTS_REMOVE = 1 << 10; 374 375 /** 376 * Flag indicating that a document is an archive, and it's contents can be 377 * obtained via {@link DocumentsProvider#queryChildDocuments}. 378 * <p> 379 * The <em>provider</em> support library offers utility classes to add common 380 * archive support. 381 * 382 * @see #COLUMN_FLAGS 383 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 384 * @hide 385 */ 386 public static final int FLAG_ARCHIVE = 1 << 15; 387 388 /** 389 * Flag indicating that a document is not complete, likely its 390 * contents are being downloaded. Partial files cannot be opened, 391 * copied, moved in the UI. But they can be deleted and retried 392 * if they represent a failed download. 393 * 394 * @see #COLUMN_FLAGS 395 * @hide 396 */ 397 public static final int FLAG_PARTIAL = 1 << 16; 398 } 399 400 /** 401 * Constants related to a root of documents, including {@link Cursor} column 402 * names and flags. A root is the start of a tree of documents, such as a 403 * physical storage device, or an account. Each root starts at the directory 404 * referenced by {@link Root#COLUMN_DOCUMENT_ID}, which can recursively 405 * contain both documents and directories. 406 * <p> 407 * All columns are <em>read-only</em> to client applications. 408 */ 409 public final static class Root { 410 private Root() { 411 } 412 413 /** 414 * Unique ID of a root. This ID is both provided by and interpreted by a 415 * {@link DocumentsProvider}, and should be treated as an opaque value 416 * by client applications. This column is required. 417 * <p> 418 * Type: STRING 419 */ 420 public static final String COLUMN_ROOT_ID = "root_id"; 421 422 /** 423 * Flags that apply to a root. This column is required. 424 * <p> 425 * Type: INTEGER (int) 426 * 427 * @see #FLAG_LOCAL_ONLY 428 * @see #FLAG_SUPPORTS_CREATE 429 * @see #FLAG_SUPPORTS_RECENTS 430 * @see #FLAG_SUPPORTS_SEARCH 431 */ 432 public static final String COLUMN_FLAGS = "flags"; 433 434 /** 435 * Icon resource ID for a root. This column is required. 436 * <p> 437 * Type: INTEGER (int) 438 */ 439 public static final String COLUMN_ICON = "icon"; 440 441 /** 442 * Title for a root, which will be shown to a user. This column is 443 * required. For a single storage service surfacing multiple accounts as 444 * different roots, this title should be the name of the service. 445 * <p> 446 * Type: STRING 447 */ 448 public static final String COLUMN_TITLE = "title"; 449 450 /** 451 * Summary for this root, which may be shown to a user. This column is 452 * optional, and may be {@code null}. For a single storage service 453 * surfacing multiple accounts as different roots, this summary should 454 * be the name of the account. 455 * <p> 456 * Type: STRING 457 */ 458 public static final String COLUMN_SUMMARY = "summary"; 459 460 /** 461 * Document which is a directory that represents the top directory of 462 * this root. This column is required. 463 * <p> 464 * Type: STRING 465 * 466 * @see Document#COLUMN_DOCUMENT_ID 467 */ 468 public static final String COLUMN_DOCUMENT_ID = "document_id"; 469 470 /** 471 * Number of bytes available in this root. This column is optional, and 472 * may be {@code null} if unknown or unbounded. 473 * <p> 474 * Type: INTEGER (long) 475 */ 476 public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; 477 478 /** 479 * Capacity of a root in bytes. This column is optional, and may be 480 * {@code null} if unknown or unbounded. 481 * <p> 482 * Type: INTEGER (long) 483 */ 484 public static final String COLUMN_CAPACITY_BYTES = "capacity_bytes"; 485 486 /** 487 * MIME types supported by this root. This column is optional, and if 488 * {@code null} the root is assumed to support all MIME types. Multiple 489 * MIME types can be separated by a newline. For example, a root 490 * supporting audio might return "audio/*\napplication/x-flac". 491 * <p> 492 * Type: STRING 493 */ 494 public static final String COLUMN_MIME_TYPES = "mime_types"; 495 496 /** {@hide} */ 497 public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; 498 499 /** 500 * Flag indicating that at least one directory under this root supports 501 * creating content. Roots with this flag will be shown when an 502 * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}. 503 * 504 * @see #COLUMN_FLAGS 505 */ 506 public static final int FLAG_SUPPORTS_CREATE = 1; 507 508 /** 509 * Flag indicating that this root offers content that is strictly local 510 * on the device. That is, no network requests are made for the content. 511 * 512 * @see #COLUMN_FLAGS 513 * @see Intent#EXTRA_LOCAL_ONLY 514 */ 515 public static final int FLAG_LOCAL_ONLY = 1 << 1; 516 517 /** 518 * Flag indicating that this root can be queried to provide recently 519 * modified documents. 520 * 521 * @see #COLUMN_FLAGS 522 * @see DocumentsContract#buildRecentDocumentsUri(String, String) 523 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 524 */ 525 public static final int FLAG_SUPPORTS_RECENTS = 1 << 2; 526 527 /** 528 * Flag indicating that this root supports search. 529 * 530 * @see #COLUMN_FLAGS 531 * @see DocumentsContract#buildSearchDocumentsUri(String, String, 532 * String) 533 * @see DocumentsProvider#querySearchDocuments(String, String, 534 * String[]) 535 */ 536 public static final int FLAG_SUPPORTS_SEARCH = 1 << 3; 537 538 /** 539 * Flag indicating that this root supports testing parent child 540 * relationships. 541 * 542 * @see #COLUMN_FLAGS 543 * @see DocumentsProvider#isChildDocument(String, String) 544 */ 545 public static final int FLAG_SUPPORTS_IS_CHILD = 1 << 4; 546 547 /** 548 * Flag indicating that this root is currently empty. This may be used 549 * to hide the root when opening documents, but the root will still be 550 * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is 551 * also set. If the value of this flag changes, such as when a root 552 * becomes non-empty, you must send a content changed notification for 553 * {@link DocumentsContract#buildRootsUri(String)}. 554 * 555 * @see #COLUMN_FLAGS 556 * @see ContentResolver#notifyChange(Uri, 557 * android.database.ContentObserver, boolean) 558 * @hide 559 */ 560 public static final int FLAG_EMPTY = 1 << 16; 561 562 /** 563 * Flag indicating that this root should only be visible to advanced 564 * users. 565 * 566 * @see #COLUMN_FLAGS 567 * @hide 568 */ 569 public static final int FLAG_ADVANCED = 1 << 17; 570 571 /** 572 * Flag indicating that this root has settings. 573 * 574 * @see #COLUMN_FLAGS 575 * @see DocumentsContract#ACTION_DOCUMENT_ROOT_SETTINGS 576 * @hide 577 */ 578 public static final int FLAG_HAS_SETTINGS = 1 << 18; 579 580 /** 581 * Flag indicating that this root is on removable SD card storage. 582 * 583 * @see #COLUMN_FLAGS 584 * @hide 585 */ 586 public static final int FLAG_REMOVABLE_SD = 1 << 19; 587 588 /** 589 * Flag indicating that this root is on removable USB storage. 590 * 591 * @see #COLUMN_FLAGS 592 * @hide 593 */ 594 public static final int FLAG_REMOVABLE_USB = 1 << 20; 595 } 596 597 /** 598 * Optional boolean flag included in a directory {@link Cursor#getExtras()} 599 * indicating that a document provider is still loading data. For example, a 600 * provider has returned some results, but is still waiting on an 601 * outstanding network request. The provider must send a content changed 602 * notification when loading is finished. 603 * 604 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, 605 * boolean) 606 */ 607 public static final String EXTRA_LOADING = "loading"; 608 609 /** 610 * Optional string included in a directory {@link Cursor#getExtras()} 611 * providing an informational message that should be shown to a user. For 612 * example, a provider may wish to indicate that not all documents are 613 * available. 614 */ 615 public static final String EXTRA_INFO = "info"; 616 617 /** 618 * Optional string included in a directory {@link Cursor#getExtras()} 619 * providing an error message that should be shown to a user. For example, a 620 * provider may wish to indicate that a network error occurred. The user may 621 * choose to retry, resulting in a new query. 622 */ 623 public static final String EXTRA_ERROR = "error"; 624 625 /** 626 * Optional result (I'm thinking boolean) answer to a question. 627 * {@hide} 628 */ 629 public static final String EXTRA_RESULT = "result"; 630 631 /** {@hide} */ 632 public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; 633 /** {@hide} */ 634 public static final String METHOD_RENAME_DOCUMENT = "android:renameDocument"; 635 /** {@hide} */ 636 public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; 637 /** {@hide} */ 638 public static final String METHOD_COPY_DOCUMENT = "android:copyDocument"; 639 /** {@hide} */ 640 public static final String METHOD_MOVE_DOCUMENT = "android:moveDocument"; 641 /** {@hide} */ 642 public static final String METHOD_IS_CHILD_DOCUMENT = "android:isChildDocument"; 643 /** {@hide} */ 644 public static final String METHOD_REMOVE_DOCUMENT = "android:removeDocument"; 645 646 /** {@hide} */ 647 public static final String EXTRA_PARENT_URI = "parentUri"; 648 /** {@hide} */ 649 public static final String EXTRA_URI = "uri"; 650 651 private static final String PATH_ROOT = "root"; 652 private static final String PATH_RECENT = "recent"; 653 private static final String PATH_DOCUMENT = "document"; 654 private static final String PATH_CHILDREN = "children"; 655 private static final String PATH_SEARCH = "search"; 656 private static final String PATH_TREE = "tree"; 657 658 private static final String PARAM_QUERY = "query"; 659 private static final String PARAM_MANAGE = "manage"; 660 661 /** 662 * Build URI representing the roots of a document provider. When queried, a 663 * provider will return one or more rows with columns defined by 664 * {@link Root}. 665 * 666 * @see DocumentsProvider#queryRoots(String[]) 667 */ 668 public static Uri buildRootsUri(String authority) { 669 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 670 .authority(authority).appendPath(PATH_ROOT).build(); 671 } 672 673 /** 674 * Build URI representing the given {@link Root#COLUMN_ROOT_ID} in a 675 * document provider. 676 * 677 * @see #getRootId(Uri) 678 */ 679 public static Uri buildRootUri(String authority, String rootId) { 680 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 681 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); 682 } 683 684 /** 685 * Builds URI for user home directory on external (local) storage. 686 * {@hide} 687 */ 688 public static Uri buildHomeUri() { 689 // TODO: Avoid this type of interpackage copying. Added here to avoid 690 // direct coupling, but not ideal. 691 return DocumentsContract.buildRootUri("com.android.externalstorage.documents", "home"); 692 } 693 694 /** 695 * Build URI representing the recently modified documents of a specific root 696 * in a document provider. When queried, a provider will return zero or more 697 * rows with columns defined by {@link Document}. 698 * 699 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 700 * @see #getRootId(Uri) 701 */ 702 public static Uri buildRecentDocumentsUri(String authority, String rootId) { 703 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 704 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) 705 .appendPath(PATH_RECENT).build(); 706 } 707 708 /** 709 * Build URI representing access to descendant documents of the given 710 * {@link Document#COLUMN_DOCUMENT_ID}. 711 * 712 * @see #getTreeDocumentId(Uri) 713 */ 714 public static Uri buildTreeDocumentUri(String authority, String documentId) { 715 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 716 .appendPath(PATH_TREE).appendPath(documentId).build(); 717 } 718 719 /** 720 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in 721 * a document provider. When queried, a provider will return a single row 722 * with columns defined by {@link Document}. 723 * 724 * @see DocumentsProvider#queryDocument(String, String[]) 725 * @see #getDocumentId(Uri) 726 */ 727 public static Uri buildDocumentUri(String authority, String documentId) { 728 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 729 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); 730 } 731 732 /** 733 * Build URI representing the target {@link Document#COLUMN_DOCUMENT_ID} in 734 * a document provider. When queried, a provider will return a single row 735 * with columns defined by {@link Document}. 736 * <p> 737 * However, instead of directly accessing the target document, the returned 738 * URI will leverage access granted through a subtree URI, typically 739 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target document 740 * must be a descendant (child, grandchild, etc) of the subtree. 741 * <p> 742 * This is typically used to access documents under a user-selected 743 * directory tree, since it doesn't require the user to separately confirm 744 * each new document access. 745 * 746 * @param treeUri the subtree to leverage to gain access to the target 747 * document. The target directory must be a descendant of this 748 * subtree. 749 * @param documentId the target document, which the caller may not have 750 * direct access to. 751 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 752 * @see DocumentsProvider#isChildDocument(String, String) 753 * @see #buildDocumentUri(String, String) 754 */ 755 public static Uri buildDocumentUriUsingTree(Uri treeUri, String documentId) { 756 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 757 .authority(treeUri.getAuthority()).appendPath(PATH_TREE) 758 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) 759 .appendPath(documentId).build(); 760 } 761 762 /** {@hide} */ 763 public static Uri buildDocumentUriMaybeUsingTree(Uri baseUri, String documentId) { 764 if (isTreeUri(baseUri)) { 765 return buildDocumentUriUsingTree(baseUri, documentId); 766 } else { 767 return buildDocumentUri(baseUri.getAuthority(), documentId); 768 } 769 } 770 771 /** 772 * Build URI representing the children of the target directory in a document 773 * provider. When queried, a provider will return zero or more rows with 774 * columns defined by {@link Document}. 775 * 776 * @param parentDocumentId the document to return children for, which must 777 * be a directory with MIME type of 778 * {@link Document#MIME_TYPE_DIR}. 779 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 780 * @see #getDocumentId(Uri) 781 */ 782 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { 783 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 784 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) 785 .build(); 786 } 787 788 /** 789 * Build URI representing the children of the target directory in a document 790 * provider. When queried, a provider will return zero or more rows with 791 * columns defined by {@link Document}. 792 * <p> 793 * However, instead of directly accessing the target directory, the returned 794 * URI will leverage access granted through a subtree URI, typically 795 * returned by {@link Intent#ACTION_OPEN_DOCUMENT_TREE}. The target 796 * directory must be a descendant (child, grandchild, etc) of the subtree. 797 * <p> 798 * This is typically used to access documents under a user-selected 799 * directory tree, since it doesn't require the user to separately confirm 800 * each new document access. 801 * 802 * @param treeUri the subtree to leverage to gain access to the target 803 * document. The target directory must be a descendant of this 804 * subtree. 805 * @param parentDocumentId the document to return children for, which the 806 * caller may not have direct access to, and which must be a 807 * directory with MIME type of {@link Document#MIME_TYPE_DIR}. 808 * @see Intent#ACTION_OPEN_DOCUMENT_TREE 809 * @see DocumentsProvider#isChildDocument(String, String) 810 * @see #buildChildDocumentsUri(String, String) 811 */ 812 public static Uri buildChildDocumentsUriUsingTree(Uri treeUri, String parentDocumentId) { 813 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 814 .authority(treeUri.getAuthority()).appendPath(PATH_TREE) 815 .appendPath(getTreeDocumentId(treeUri)).appendPath(PATH_DOCUMENT) 816 .appendPath(parentDocumentId).appendPath(PATH_CHILDREN).build(); 817 } 818 819 /** 820 * Build URI representing a search for matching documents under a specific 821 * root in a document provider. When queried, a provider will return zero or 822 * more rows with columns defined by {@link Document}. 823 * 824 * @see DocumentsProvider#querySearchDocuments(String, String, String[]) 825 * @see #getRootId(Uri) 826 * @see #getSearchDocumentsQuery(Uri) 827 */ 828 public static Uri buildSearchDocumentsUri( 829 String authority, String rootId, String query) { 830 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 831 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH) 832 .appendQueryParameter(PARAM_QUERY, query).build(); 833 } 834 835 /** 836 * Test if the given URI represents a {@link Document} backed by a 837 * {@link DocumentsProvider}. 838 * 839 * @see #buildDocumentUri(String, String) 840 * @see #buildDocumentUriUsingTree(Uri, String) 841 */ 842 public static boolean isDocumentUri(Context context, @Nullable Uri uri) { 843 if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) { 844 final List<String> paths = uri.getPathSegments(); 845 if (paths.size() == 2) { 846 return PATH_DOCUMENT.equals(paths.get(0)); 847 } else if (paths.size() == 4) { 848 return PATH_TREE.equals(paths.get(0)) && PATH_DOCUMENT.equals(paths.get(2)); 849 } 850 } 851 return false; 852 } 853 854 /** {@hide} */ 855 public static boolean isRootUri(Context context, @Nullable Uri uri) { 856 if (isContentUri(uri) && isDocumentsProvider(context, uri.getAuthority())) { 857 final List<String> paths = uri.getPathSegments(); 858 return (paths.size() == 2 && PATH_ROOT.equals(paths.get(0))); 859 } 860 return false; 861 } 862 863 /** {@hide} */ 864 public static boolean isContentUri(@Nullable Uri uri) { 865 return uri != null && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()); 866 } 867 868 /** 869 * Test if the given URI represents a {@link Document} tree. 870 * 871 * @see #buildTreeDocumentUri(String, String) 872 * @see #getTreeDocumentId(Uri) 873 */ 874 public static boolean isTreeUri(Uri uri) { 875 final List<String> paths = uri.getPathSegments(); 876 return (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))); 877 } 878 879 private static boolean isDocumentsProvider(Context context, String authority) { 880 final Intent intent = new Intent(PROVIDER_INTERFACE); 881 final List<ResolveInfo> infos = context.getPackageManager() 882 .queryIntentContentProviders(intent, 0); 883 for (ResolveInfo info : infos) { 884 if (authority.equals(info.providerInfo.authority)) { 885 return true; 886 } 887 } 888 return false; 889 } 890 891 /** 892 * Extract the {@link Root#COLUMN_ROOT_ID} from the given URI. 893 */ 894 public static String getRootId(Uri rootUri) { 895 final List<String> paths = rootUri.getPathSegments(); 896 if (paths.size() >= 2 && PATH_ROOT.equals(paths.get(0))) { 897 return paths.get(1); 898 } 899 throw new IllegalArgumentException("Invalid URI: " + rootUri); 900 } 901 902 /** 903 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given URI. 904 * 905 * @see #isDocumentUri(Context, Uri) 906 */ 907 public static String getDocumentId(Uri documentUri) { 908 final List<String> paths = documentUri.getPathSegments(); 909 if (paths.size() >= 2 && PATH_DOCUMENT.equals(paths.get(0))) { 910 return paths.get(1); 911 } 912 if (paths.size() >= 4 && PATH_TREE.equals(paths.get(0)) 913 && PATH_DOCUMENT.equals(paths.get(2))) { 914 return paths.get(3); 915 } 916 throw new IllegalArgumentException("Invalid URI: " + documentUri); 917 } 918 919 /** 920 * Extract the via {@link Document#COLUMN_DOCUMENT_ID} from the given URI. 921 */ 922 public static String getTreeDocumentId(Uri documentUri) { 923 final List<String> paths = documentUri.getPathSegments(); 924 if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) { 925 return paths.get(1); 926 } 927 throw new IllegalArgumentException("Invalid URI: " + documentUri); 928 } 929 930 /** 931 * Extract the search query from a URI built by 932 * {@link #buildSearchDocumentsUri(String, String, String)}. 933 */ 934 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { 935 return searchDocumentsUri.getQueryParameter(PARAM_QUERY); 936 } 937 938 /** {@hide} */ 939 public static Uri setManageMode(Uri uri) { 940 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); 941 } 942 943 /** {@hide} */ 944 public static boolean isManageMode(Uri uri) { 945 return uri.getBooleanQueryParameter(PARAM_MANAGE, false); 946 } 947 948 /** 949 * Return thumbnail representing the document at the given URI. Callers are 950 * responsible for their own in-memory caching. 951 * 952 * @param documentUri document to return thumbnail for, which must have 953 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. 954 * @param size optimal thumbnail size desired. A provider may return a 955 * thumbnail of a different size, but never more than double the 956 * requested size. 957 * @param signal signal used to indicate if caller is no longer interested 958 * in the thumbnail. 959 * @return decoded thumbnail, or {@code null} if problem was encountered. 960 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 961 * android.os.CancellationSignal) 962 */ 963 public static Bitmap getDocumentThumbnail( 964 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 965 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 966 documentUri.getAuthority()); 967 try { 968 return getDocumentThumbnail(client, documentUri, size, signal); 969 } catch (Exception e) { 970 if (!(e instanceof OperationCanceledException)) { 971 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 972 } 973 return null; 974 } finally { 975 ContentProviderClient.releaseQuietly(client); 976 } 977 } 978 979 /** {@hide} */ 980 public static Bitmap getDocumentThumbnail( 981 ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) 982 throws RemoteException, IOException { 983 final Bundle openOpts = new Bundle(); 984 openOpts.putParcelable(ContentResolver.EXTRA_SIZE, size); 985 986 AssetFileDescriptor afd = null; 987 Bitmap bitmap = null; 988 try { 989 afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); 990 991 final FileDescriptor fd = afd.getFileDescriptor(); 992 final long offset = afd.getStartOffset(); 993 994 // Try seeking on the returned FD, since it gives us the most 995 // optimal decode path; otherwise fall back to buffering. 996 BufferedInputStream is = null; 997 try { 998 Os.lseek(fd, offset, SEEK_SET); 999 } catch (ErrnoException e) { 1000 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); 1001 is.mark(THUMBNAIL_BUFFER_SIZE); 1002 } 1003 1004 // We requested a rough thumbnail size, but the remote size may have 1005 // returned something giant, so defensively scale down as needed. 1006 final BitmapFactory.Options opts = new BitmapFactory.Options(); 1007 opts.inJustDecodeBounds = true; 1008 if (is != null) { 1009 BitmapFactory.decodeStream(is, null, opts); 1010 } else { 1011 BitmapFactory.decodeFileDescriptor(fd, null, opts); 1012 } 1013 1014 final int widthSample = opts.outWidth / size.x; 1015 final int heightSample = opts.outHeight / size.y; 1016 1017 opts.inJustDecodeBounds = false; 1018 opts.inSampleSize = Math.min(widthSample, heightSample); 1019 if (is != null) { 1020 is.reset(); 1021 bitmap = BitmapFactory.decodeStream(is, null, opts); 1022 } else { 1023 try { 1024 Os.lseek(fd, offset, SEEK_SET); 1025 } catch (ErrnoException e) { 1026 e.rethrowAsIOException(); 1027 } 1028 bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts); 1029 } 1030 1031 // Transform the bitmap if requested. We use a side-channel to 1032 // communicate the orientation, since EXIF thumbnails don't contain 1033 // the rotation flags of the original image. 1034 final Bundle extras = afd.getExtras(); 1035 final int orientation = (extras != null) ? extras.getInt(EXTRA_ORIENTATION, 0) : 0; 1036 if (orientation != 0) { 1037 final int width = bitmap.getWidth(); 1038 final int height = bitmap.getHeight(); 1039 1040 final Matrix m = new Matrix(); 1041 m.setRotate(orientation, width / 2, height / 2); 1042 bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, m, false); 1043 } 1044 } finally { 1045 IoUtils.closeQuietly(afd); 1046 } 1047 1048 return bitmap; 1049 } 1050 1051 /** 1052 * Create a new document with given MIME type and display name. 1053 * 1054 * @param parentDocumentUri directory with 1055 * {@link Document#FLAG_DIR_SUPPORTS_CREATE} 1056 * @param mimeType MIME type of new document 1057 * @param displayName name of new document 1058 * @return newly created document, or {@code null} if failed 1059 */ 1060 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, 1061 String mimeType, String displayName) { 1062 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 1063 parentDocumentUri.getAuthority()); 1064 try { 1065 return createDocument(client, parentDocumentUri, mimeType, displayName); 1066 } catch (Exception e) { 1067 Log.w(TAG, "Failed to create document", e); 1068 return null; 1069 } finally { 1070 ContentProviderClient.releaseQuietly(client); 1071 } 1072 } 1073 1074 /** {@hide} */ 1075 public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, 1076 String mimeType, String displayName) throws RemoteException { 1077 final Bundle in = new Bundle(); 1078 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); 1079 in.putString(Document.COLUMN_MIME_TYPE, mimeType); 1080 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 1081 1082 final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); 1083 return out.getParcelable(DocumentsContract.EXTRA_URI); 1084 } 1085 1086 /** {@hide} */ 1087 public static boolean isChildDocument(ContentProviderClient client, Uri parentDocumentUri, 1088 Uri childDocumentUri) throws RemoteException { 1089 1090 final Bundle in = new Bundle(); 1091 in.putParcelable(DocumentsContract.EXTRA_URI, parentDocumentUri); 1092 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, childDocumentUri); 1093 1094 final Bundle out = client.call(METHOD_IS_CHILD_DOCUMENT, null, in); 1095 if (out == null) { 1096 throw new RemoteException("Failed to get a reponse from isChildDocument query."); 1097 } 1098 if (!out.containsKey(DocumentsContract.EXTRA_RESULT)) { 1099 throw new RemoteException("Response did not include result field.."); 1100 } 1101 return out.getBoolean(DocumentsContract.EXTRA_RESULT); 1102 } 1103 1104 /** 1105 * Change the display name of an existing document. 1106 * <p> 1107 * If the underlying provider needs to create a new 1108 * {@link Document#COLUMN_DOCUMENT_ID} to represent the updated display 1109 * name, that new document is returned and the original document is no 1110 * longer valid. Otherwise, the original document is returned. 1111 * 1112 * @param documentUri document with {@link Document#FLAG_SUPPORTS_RENAME} 1113 * @param displayName updated name for document 1114 * @return the existing or new document after the rename, or {@code null} if 1115 * failed. 1116 */ 1117 public static Uri renameDocument(ContentResolver resolver, Uri documentUri, 1118 String displayName) { 1119 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 1120 documentUri.getAuthority()); 1121 try { 1122 return renameDocument(client, documentUri, displayName); 1123 } catch (Exception e) { 1124 Log.w(TAG, "Failed to rename document", e); 1125 return null; 1126 } finally { 1127 ContentProviderClient.releaseQuietly(client); 1128 } 1129 } 1130 1131 /** {@hide} */ 1132 public static Uri renameDocument(ContentProviderClient client, Uri documentUri, 1133 String displayName) throws RemoteException { 1134 final Bundle in = new Bundle(); 1135 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 1136 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 1137 1138 final Bundle out = client.call(METHOD_RENAME_DOCUMENT, null, in); 1139 final Uri outUri = out.getParcelable(DocumentsContract.EXTRA_URI); 1140 return (outUri != null) ? outUri : documentUri; 1141 } 1142 1143 /** 1144 * Delete the given document. 1145 * 1146 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} 1147 * @return if the document was deleted successfully. 1148 */ 1149 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 1150 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 1151 documentUri.getAuthority()); 1152 try { 1153 deleteDocument(client, documentUri); 1154 return true; 1155 } catch (Exception e) { 1156 Log.w(TAG, "Failed to delete document", e); 1157 return false; 1158 } finally { 1159 ContentProviderClient.releaseQuietly(client); 1160 } 1161 } 1162 1163 /** {@hide} */ 1164 public static void deleteDocument(ContentProviderClient client, Uri documentUri) 1165 throws RemoteException { 1166 final Bundle in = new Bundle(); 1167 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 1168 1169 client.call(METHOD_DELETE_DOCUMENT, null, in); 1170 } 1171 1172 /** 1173 * Copies the given document. 1174 * 1175 * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY} 1176 * @param targetParentDocumentUri document which will become a parent of the source 1177 * document's copy. 1178 * @return the copied document, or {@code null} if failed. 1179 */ 1180 public static Uri copyDocument(ContentResolver resolver, Uri sourceDocumentUri, 1181 Uri targetParentDocumentUri) { 1182 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 1183 sourceDocumentUri.getAuthority()); 1184 try { 1185 return copyDocument(client, sourceDocumentUri, targetParentDocumentUri); 1186 } catch (Exception e) { 1187 Log.w(TAG, "Failed to copy document", e); 1188 return null; 1189 } finally { 1190 ContentProviderClient.releaseQuietly(client); 1191 } 1192 } 1193 1194 /** {@hide} */ 1195 public static Uri copyDocument(ContentProviderClient client, Uri sourceDocumentUri, 1196 Uri targetParentDocumentUri) throws RemoteException { 1197 final Bundle in = new Bundle(); 1198 in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); 1199 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); 1200 1201 final Bundle out = client.call(METHOD_COPY_DOCUMENT, null, in); 1202 return out.getParcelable(DocumentsContract.EXTRA_URI); 1203 } 1204 1205 /** 1206 * Moves the given document under a new parent. 1207 * 1208 * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE} 1209 * @param sourceParentDocumentUri parent document of the document to move. 1210 * @param targetParentDocumentUri document which will become a new parent of the source 1211 * document. 1212 * @return the moved document, or {@code null} if failed. 1213 */ 1214 public static Uri moveDocument(ContentResolver resolver, Uri sourceDocumentUri, 1215 Uri sourceParentDocumentUri, Uri targetParentDocumentUri) { 1216 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 1217 sourceDocumentUri.getAuthority()); 1218 try { 1219 return moveDocument(client, sourceDocumentUri, sourceParentDocumentUri, 1220 targetParentDocumentUri); 1221 } catch (Exception e) { 1222 Log.w(TAG, "Failed to move document", e); 1223 return null; 1224 } finally { 1225 ContentProviderClient.releaseQuietly(client); 1226 } 1227 } 1228 1229 /** {@hide} */ 1230 public static Uri moveDocument(ContentProviderClient client, Uri sourceDocumentUri, 1231 Uri sourceParentDocumentUri, Uri targetParentDocumentUri) throws RemoteException { 1232 final Bundle in = new Bundle(); 1233 in.putParcelable(DocumentsContract.EXTRA_URI, sourceDocumentUri); 1234 in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, sourceParentDocumentUri); 1235 in.putParcelable(DocumentsContract.EXTRA_TARGET_URI, targetParentDocumentUri); 1236 1237 final Bundle out = client.call(METHOD_MOVE_DOCUMENT, null, in); 1238 return out.getParcelable(DocumentsContract.EXTRA_URI); 1239 } 1240 1241 /** 1242 * Removes the given document from a parent directory. 1243 * 1244 * <p>In contrast to {@link #deleteDocument} it requires specifying the parent. 1245 * This method is especially useful if the document can be in multiple parents. 1246 * 1247 * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE} 1248 * @param parentDocumentUri parent document of the document to remove. 1249 * @return true if the document was removed successfully. 1250 */ 1251 public static boolean removeDocument(ContentResolver resolver, Uri documentUri, 1252 Uri parentDocumentUri) { 1253 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 1254 documentUri.getAuthority()); 1255 try { 1256 removeDocument(client, documentUri, parentDocumentUri); 1257 return true; 1258 } catch (Exception e) { 1259 Log.w(TAG, "Failed to remove document", e); 1260 return false; 1261 } finally { 1262 ContentProviderClient.releaseQuietly(client); 1263 } 1264 } 1265 1266 /** {@hide} */ 1267 public static void removeDocument(ContentProviderClient client, Uri documentUri, 1268 Uri parentDocumentUri) throws RemoteException { 1269 final Bundle in = new Bundle(); 1270 in.putParcelable(DocumentsContract.EXTRA_URI, documentUri); 1271 in.putParcelable(DocumentsContract.EXTRA_PARENT_URI, parentDocumentUri); 1272 1273 client.call(METHOD_REMOVE_DOCUMENT, null, in); 1274 } 1275 1276 /** 1277 * Open the given image for thumbnail purposes, using any embedded EXIF 1278 * thumbnail if available, and providing orientation hints from the parent 1279 * image. 1280 * 1281 * @hide 1282 */ 1283 public static AssetFileDescriptor openImageThumbnail(File file) throws FileNotFoundException { 1284 final ParcelFileDescriptor pfd = ParcelFileDescriptor.open( 1285 file, ParcelFileDescriptor.MODE_READ_ONLY); 1286 Bundle extras = null; 1287 1288 try { 1289 final ExifInterface exif = new ExifInterface(file.getAbsolutePath()); 1290 1291 switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) { 1292 case ExifInterface.ORIENTATION_ROTATE_90: 1293 extras = new Bundle(1); 1294 extras.putInt(EXTRA_ORIENTATION, 90); 1295 break; 1296 case ExifInterface.ORIENTATION_ROTATE_180: 1297 extras = new Bundle(1); 1298 extras.putInt(EXTRA_ORIENTATION, 180); 1299 break; 1300 case ExifInterface.ORIENTATION_ROTATE_270: 1301 extras = new Bundle(1); 1302 extras.putInt(EXTRA_ORIENTATION, 270); 1303 break; 1304 } 1305 1306 final long[] thumb = exif.getThumbnailRange(); 1307 if (thumb != null) { 1308 return new AssetFileDescriptor(pfd, thumb[0], thumb[1], extras); 1309 } 1310 } catch (IOException e) { 1311 } 1312 1313 return new AssetFileDescriptor(pfd, 0, AssetFileDescriptor.UNKNOWN_LENGTH, extras); 1314 } 1315 } 1316