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