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