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