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