DocumentsContract.java revision a61dc8e03e6e863005b3a4629ca8f3801d33d3c4
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 /** 257 * Constants related to a root of documents, including {@link Cursor} 258 * columns names and flags. 259 * <p> 260 * All columns are <em>read-only</em> to client applications. 261 */ 262 public final static class Root { 263 private Root() { 264 } 265 266 /** 267 * Unique ID of a root. This ID is both provided by and interpreted by a 268 * {@link DocumentsProvider}, and should be treated as an opaque value 269 * by client applications. 270 * <p> 271 * Type: STRING 272 */ 273 public static final String COLUMN_ROOT_ID = "root_id"; 274 275 /** 276 * Type of a root, used for clustering when presenting multiple roots to 277 * a user. 278 * <p> 279 * Type: INTEGER (int) 280 * 281 * @see #ROOT_TYPE_SERVICE 282 * @see #ROOT_TYPE_SHORTCUT 283 * @see #ROOT_TYPE_DEVICE 284 */ 285 public static final String COLUMN_ROOT_TYPE = "root_type"; 286 287 /** 288 * Flags that apply to a root. 289 * <p> 290 * Type: INTEGER (int) 291 * 292 * @see #FLAG_LOCAL_ONLY 293 * @see #FLAG_SUPPORTS_CREATE 294 * @see #FLAG_ADVANCED 295 * @see #FLAG_PROVIDES_AUDIO 296 * @see #FLAG_PROVIDES_IMAGES 297 * @see #FLAG_PROVIDES_VIDEO 298 */ 299 public static final String COLUMN_FLAGS = "flags"; 300 301 /** 302 * Icon resource ID for a root. 303 * <p> 304 * Type: INTEGER (int) 305 */ 306 public static final String COLUMN_ICON = "icon"; 307 308 /** 309 * Title for a root, which will be shown to a user. 310 * <p> 311 * Type: STRING 312 */ 313 public static final String COLUMN_TITLE = "title"; 314 315 /** 316 * Summary for this root, which may be shown to a user. The summary may 317 * be {@code null}. 318 * <p> 319 * Type: STRING 320 */ 321 public static final String COLUMN_SUMMARY = "summary"; 322 323 /** 324 * Document which is a directory that represents the top directory of 325 * this root. 326 * <p> 327 * Type: STRING 328 * 329 * @see Document#COLUMN_DOCUMENT_ID 330 */ 331 public static final String COLUMN_DOCUMENT_ID = "document_id"; 332 333 /** 334 * Number of bytes available in this root, or {@code null} if unknown or 335 * unbounded. 336 * <p> 337 * Type: INTEGER (long) 338 */ 339 public static final String COLUMN_AVAILABLE_BYTES = "available_bytes"; 340 341 /** 342 * MIME types supported by this root, or {@code null} if the root 343 * supports all MIME types. Multiple MIME types can be separated by a 344 * newline. For example, a root supporting audio might use 345 * "audio/*\napplication/x-flac". 346 * <p> 347 * Type: String 348 */ 349 public static final String COLUMN_MIME_TYPES = "mime_types"; 350 351 /** {@hide} */ 352 public static final String MIME_TYPE_ITEM = "vnd.android.document/root"; 353 354 /** 355 * Type of root that represents a storage service, such as a cloud-based 356 * service. 357 * 358 * @see #COLUMN_ROOT_TYPE 359 */ 360 public static final int ROOT_TYPE_SERVICE = 1; 361 362 /** 363 * Type of root that represents a shortcut to content that may be 364 * available elsewhere through another storage root. 365 * 366 * @see #COLUMN_ROOT_TYPE 367 */ 368 public static final int ROOT_TYPE_SHORTCUT = 2; 369 370 /** 371 * Type of root that represents a physical storage device. 372 * 373 * @see #COLUMN_ROOT_TYPE 374 */ 375 public static final int ROOT_TYPE_DEVICE = 3; 376 377 /** 378 * Flag indicating that at least one directory under this root supports 379 * creating content. Roots with this flag will be shown when an 380 * application interacts with {@link Intent#ACTION_CREATE_DOCUMENT}. 381 * 382 * @see #COLUMN_FLAGS 383 */ 384 public static final int FLAG_SUPPORTS_CREATE = 1; 385 386 /** 387 * Flag indicating that this root offers content that is strictly local 388 * on the device. That is, no network requests are made for the content. 389 * 390 * @see #COLUMN_FLAGS 391 * @see Intent#EXTRA_LOCAL_ONLY 392 */ 393 public static final int FLAG_LOCAL_ONLY = 1 << 1; 394 395 /** 396 * Flag indicating that this root should only be visible to advanced 397 * users. 398 * 399 * @see #COLUMN_FLAGS 400 */ 401 public static final int FLAG_ADVANCED = 1 << 2; 402 403 /** 404 * Flag indicating that this root can report recently modified 405 * documents. 406 * 407 * @see #COLUMN_FLAGS 408 * @see DocumentsContract#buildRecentDocumentsUri(String, String) 409 */ 410 public static final int FLAG_SUPPORTS_RECENTS = 1 << 3; 411 } 412 413 /** 414 * Optional boolean flag included in a directory {@link Cursor#getExtras()} 415 * indicating that a document provider is still loading data. For example, a 416 * provider has returned some results, but is still waiting on an 417 * outstanding network request. The provider must send a content changed 418 * notification when loading is finished. 419 * 420 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, 421 * boolean) 422 */ 423 public static final String EXTRA_LOADING = "loading"; 424 425 /** 426 * Optional string included in a directory {@link Cursor#getExtras()} 427 * providing an informational message that should be shown to a user. For 428 * example, a provider may wish to indicate that not all documents are 429 * available. 430 */ 431 public static final String EXTRA_INFO = "info"; 432 433 /** 434 * Optional string included in a directory {@link Cursor#getExtras()} 435 * providing an error message that should be shown to a user. For example, a 436 * provider may wish to indicate that a network error occurred. The user may 437 * choose to retry, resulting in a new query. 438 */ 439 public static final String EXTRA_ERROR = "error"; 440 441 /** {@hide} */ 442 public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; 443 /** {@hide} */ 444 public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; 445 446 /** {@hide} */ 447 public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; 448 449 private static final String PATH_ROOT = "root"; 450 private static final String PATH_RECENT = "recent"; 451 private static final String PATH_DOCUMENT = "document"; 452 private static final String PATH_CHILDREN = "children"; 453 private static final String PATH_SEARCH = "search"; 454 455 private static final String PARAM_QUERY = "query"; 456 457 /** 458 * Build Uri representing the roots of a document provider. When queried, a 459 * provider will return one or more rows with columns defined by 460 * {@link Root}. 461 * 462 * @see DocumentsProvider#queryRoots(String[]) 463 */ 464 public static Uri buildRootsUri(String authority) { 465 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 466 .authority(authority).appendPath(PATH_ROOT).build(); 467 } 468 469 /** 470 * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a 471 * document provider. 472 * 473 * @see #getRootId(Uri) 474 */ 475 public static Uri buildRootUri(String authority, String rootId) { 476 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 477 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); 478 } 479 480 /** 481 * Build Uri representing the recently modified documents of a specific 482 * root. When queried, a provider will return zero or more rows with columns 483 * defined by {@link Document}. 484 * 485 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 486 * @see #getRootId(Uri) 487 */ 488 public static Uri buildRecentDocumentsUri(String authority, String rootId) { 489 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 490 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) 491 .appendPath(PATH_RECENT).build(); 492 } 493 494 /** 495 * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a 496 * document provider. When queried, a provider will return a single row with 497 * columns defined by {@link Document}. 498 * 499 * @see DocumentsProvider#queryDocument(String, String[]) 500 * @see #getDocumentId(Uri) 501 */ 502 public static Uri buildDocumentUri(String authority, String documentId) { 503 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 504 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); 505 } 506 507 /** 508 * Build Uri representing the children of the given directory in a document 509 * provider. When queried, a provider will return zero or more rows with 510 * columns defined by {@link Document}. 511 * 512 * @param parentDocumentId the document to return children for, which must 513 * be a directory with MIME type of 514 * {@link Document#MIME_TYPE_DIR}. 515 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 516 * @see #getDocumentId(Uri) 517 */ 518 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { 519 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 520 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) 521 .build(); 522 } 523 524 /** 525 * Build Uri representing a search for matching documents under a specific 526 * directory in a document provider. When queried, a provider will return 527 * zero or more rows with columns defined by {@link Document}. 528 * 529 * @param parentDocumentId the document to return children for, which must 530 * be both a directory with MIME type of 531 * {@link Document#MIME_TYPE_DIR} and have 532 * {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set. 533 * @see DocumentsProvider#querySearchDocuments(String, String, String[]) 534 * @see #getDocumentId(Uri) 535 * @see #getSearchDocumentsQuery(Uri) 536 */ 537 public static Uri buildSearchDocumentsUri( 538 String authority, String parentDocumentId, String query) { 539 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 540 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH) 541 .appendQueryParameter(PARAM_QUERY, query).build(); 542 } 543 544 /** 545 * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri. 546 */ 547 public static String getRootId(Uri rootUri) { 548 final List<String> paths = rootUri.getPathSegments(); 549 if (paths.size() < 2) { 550 throw new IllegalArgumentException("Not a root: " + rootUri); 551 } 552 if (!PATH_ROOT.equals(paths.get(0))) { 553 throw new IllegalArgumentException("Not a root: " + rootUri); 554 } 555 return paths.get(1); 556 } 557 558 /** 559 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri. 560 */ 561 public static String getDocumentId(Uri documentUri) { 562 final List<String> paths = documentUri.getPathSegments(); 563 if (paths.size() < 2) { 564 throw new IllegalArgumentException("Not a document: " + documentUri); 565 } 566 if (!PATH_DOCUMENT.equals(paths.get(0))) { 567 throw new IllegalArgumentException("Not a document: " + documentUri); 568 } 569 return paths.get(1); 570 } 571 572 /** 573 * Extract the search query from a Uri built by 574 * {@link #buildSearchDocumentsUri(String, String, String)}. 575 */ 576 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { 577 return searchDocumentsUri.getQueryParameter(PARAM_QUERY); 578 } 579 580 /** 581 * Return list of all documents that the calling package has "open." These 582 * are Uris matching {@link DocumentsContract} to which persistent 583 * read/write access has been granted, usually through 584 * {@link Intent#ACTION_OPEN_DOCUMENT} or 585 * {@link Intent#ACTION_CREATE_DOCUMENT}. 586 * 587 * @see Context#grantUriPermission(String, Uri, int) 588 * @see Context#revokeUriPermission(Uri, int) 589 * @see ContentResolver#getIncomingUriPermissionGrants(int, int) 590 */ 591 public static Uri[] getOpenDocuments(Context context) { 592 final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION 593 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION; 594 final Uri[] uris = context.getContentResolver() 595 .getIncomingUriPermissionGrants(openedFlags, openedFlags); 596 597 // Filter to only include document providers 598 final PackageManager pm = context.getPackageManager(); 599 final List<Uri> result = Lists.newArrayList(); 600 for (Uri uri : uris) { 601 final ProviderInfo info = pm.resolveContentProvider( 602 uri.getAuthority(), PackageManager.GET_META_DATA); 603 if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) { 604 result.add(uri); 605 } 606 } 607 608 return result.toArray(new Uri[result.size()]); 609 } 610 611 /** 612 * Return thumbnail representing the document at the given Uri. Callers are 613 * responsible for their own in-memory caching. 614 * 615 * @param documentUri document to return thumbnail for, which must have 616 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. 617 * @param size optimal thumbnail size desired. A provider may return a 618 * thumbnail of a different size, but never more than double the 619 * requested size. 620 * @param signal signal used to indicate that caller is no longer interested 621 * in the thumbnail. 622 * @return decoded thumbnail, or {@code null} if problem was encountered. 623 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 624 * android.os.CancellationSignal) 625 */ 626 public static Bitmap getDocumentThumbnail( 627 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 628 final Bundle openOpts = new Bundle(); 629 openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); 630 631 AssetFileDescriptor afd = null; 632 try { 633 afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); 634 635 final FileDescriptor fd = afd.getFileDescriptor(); 636 final long offset = afd.getStartOffset(); 637 final long length = afd.getDeclaredLength(); 638 639 // Some thumbnails might be a region inside a larger file, such as 640 // an EXIF thumbnail. Since BitmapFactory aggressively seeks around 641 // the entire file, we read the region manually. 642 byte[] region = null; 643 if (offset > 0 && length <= 64 * KB_IN_BYTES) { 644 region = new byte[(int) length]; 645 Libcore.os.lseek(fd, offset, SEEK_SET); 646 if (IoBridge.read(fd, region, 0, region.length) != region.length) { 647 region = null; 648 } 649 } 650 651 // We requested a rough thumbnail size, but the remote size may have 652 // returned something giant, so defensively scale down as needed. 653 final BitmapFactory.Options opts = new BitmapFactory.Options(); 654 opts.inJustDecodeBounds = true; 655 if (region != null) { 656 BitmapFactory.decodeByteArray(region, 0, region.length, opts); 657 } else { 658 BitmapFactory.decodeFileDescriptor(fd, null, opts); 659 } 660 661 final int widthSample = opts.outWidth / size.x; 662 final int heightSample = opts.outHeight / size.y; 663 664 opts.inJustDecodeBounds = false; 665 opts.inSampleSize = Math.min(widthSample, heightSample); 666 Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); 667 if (region != null) { 668 return BitmapFactory.decodeByteArray(region, 0, region.length, opts); 669 } else { 670 return BitmapFactory.decodeFileDescriptor(fd, null, opts); 671 } 672 } catch (ErrnoException e) { 673 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 674 return null; 675 } catch (IOException e) { 676 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 677 return null; 678 } finally { 679 IoUtils.closeQuietly(afd); 680 } 681 } 682 683 /** 684 * Create a new document with given MIME type and display name. 685 * 686 * @param parentDocumentUri directory with 687 * {@link Document#FLAG_DIR_SUPPORTS_CREATE} 688 * @param mimeType MIME type of new document 689 * @param displayName name of new document 690 * @return newly created document, or {@code null} if failed 691 */ 692 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, 693 String mimeType, String displayName) { 694 final Bundle in = new Bundle(); 695 in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); 696 in.putString(Document.COLUMN_MIME_TYPE, mimeType); 697 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 698 699 try { 700 final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in); 701 return buildDocumentUri( 702 parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); 703 } catch (Exception e) { 704 Log.w(TAG, "Failed to create document", e); 705 return null; 706 } 707 } 708 709 /** 710 * Delete the given document. 711 * 712 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} 713 */ 714 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 715 final Bundle in = new Bundle(); 716 in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); 717 718 try { 719 final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in); 720 return true; 721 } catch (Exception e) { 722 Log.w(TAG, "Failed to delete document", e); 723 return false; 724 } 725 } 726} 727