DocumentsContract.java revision 4ec973925fc2cd18f9ec0d0ca5af588564fded27
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 private static final String PARAM_MANAGE = "manage"; 463 464 /** 465 * Build Uri representing the roots of a document provider. When queried, a 466 * provider will return one or more rows with columns defined by 467 * {@link Root}. 468 * 469 * @see DocumentsProvider#queryRoots(String[]) 470 */ 471 public static Uri buildRootsUri(String authority) { 472 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 473 .authority(authority).appendPath(PATH_ROOT).build(); 474 } 475 476 /** 477 * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a 478 * document provider. 479 * 480 * @see #getRootId(Uri) 481 */ 482 public static Uri buildRootUri(String authority, String rootId) { 483 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 484 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); 485 } 486 487 /** 488 * Build Uri representing the recently modified documents of a specific 489 * root. When queried, a provider will return zero or more rows with columns 490 * defined by {@link Document}. 491 * 492 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 493 * @see #getRootId(Uri) 494 */ 495 public static Uri buildRecentDocumentsUri(String authority, String rootId) { 496 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 497 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) 498 .appendPath(PATH_RECENT).build(); 499 } 500 501 /** 502 * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a 503 * document provider. When queried, a provider will return a single row with 504 * columns defined by {@link Document}. 505 * 506 * @see DocumentsProvider#queryDocument(String, String[]) 507 * @see #getDocumentId(Uri) 508 */ 509 public static Uri buildDocumentUri(String authority, String documentId) { 510 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 511 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); 512 } 513 514 /** 515 * Build Uri representing the children of the given directory in a document 516 * provider. When queried, a provider will return zero or more rows with 517 * columns defined by {@link Document}. 518 * 519 * @param parentDocumentId the document to return children for, which must 520 * be a directory with MIME type of 521 * {@link Document#MIME_TYPE_DIR}. 522 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 523 * @see #getDocumentId(Uri) 524 */ 525 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { 526 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 527 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) 528 .build(); 529 } 530 531 /** 532 * Build Uri representing a search for matching documents under a specific 533 * directory in a document provider. When queried, a provider will return 534 * zero or more rows with columns defined by {@link Document}. 535 * 536 * @param parentDocumentId the document to return children for, which must 537 * be both a directory with MIME type of 538 * {@link Document#MIME_TYPE_DIR} and have 539 * {@link Document#FLAG_DIR_SUPPORTS_SEARCH} set. 540 * @see DocumentsProvider#querySearchDocuments(String, String, String[]) 541 * @see #getDocumentId(Uri) 542 * @see #getSearchDocumentsQuery(Uri) 543 */ 544 public static Uri buildSearchDocumentsUri( 545 String authority, String parentDocumentId, String query) { 546 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 547 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_SEARCH) 548 .appendQueryParameter(PARAM_QUERY, query).build(); 549 } 550 551 /** 552 * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri. 553 */ 554 public static String getRootId(Uri rootUri) { 555 final List<String> paths = rootUri.getPathSegments(); 556 if (paths.size() < 2) { 557 throw new IllegalArgumentException("Not a root: " + rootUri); 558 } 559 if (!PATH_ROOT.equals(paths.get(0))) { 560 throw new IllegalArgumentException("Not a root: " + rootUri); 561 } 562 return paths.get(1); 563 } 564 565 /** 566 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri. 567 */ 568 public static String getDocumentId(Uri documentUri) { 569 final List<String> paths = documentUri.getPathSegments(); 570 if (paths.size() < 2) { 571 throw new IllegalArgumentException("Not a document: " + documentUri); 572 } 573 if (!PATH_DOCUMENT.equals(paths.get(0))) { 574 throw new IllegalArgumentException("Not a document: " + documentUri); 575 } 576 return paths.get(1); 577 } 578 579 /** 580 * Extract the search query from a Uri built by 581 * {@link #buildSearchDocumentsUri(String, String, String)}. 582 */ 583 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { 584 return searchDocumentsUri.getQueryParameter(PARAM_QUERY); 585 } 586 587 /** {@hide} */ 588 public static Uri setManageMode(Uri uri) { 589 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); 590 } 591 592 /** {@hide} */ 593 public static boolean isManageMode(Uri uri) { 594 return uri.getBooleanQueryParameter(PARAM_MANAGE, false); 595 } 596 597 /** 598 * Return list of all documents that the calling package has "open." These 599 * are Uris matching {@link DocumentsContract} to which persistent 600 * read/write access has been granted, usually through 601 * {@link Intent#ACTION_OPEN_DOCUMENT} or 602 * {@link Intent#ACTION_CREATE_DOCUMENT}. 603 * 604 * @see Context#grantUriPermission(String, Uri, int) 605 * @see Context#revokeUriPermission(Uri, int) 606 * @see ContentResolver#getIncomingUriPermissionGrants(int, int) 607 */ 608 public static Uri[] getOpenDocuments(Context context) { 609 final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION 610 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION; 611 final Uri[] uris = context.getContentResolver() 612 .getIncomingUriPermissionGrants(openedFlags, openedFlags); 613 614 // Filter to only include document providers 615 final PackageManager pm = context.getPackageManager(); 616 final List<Uri> result = Lists.newArrayList(); 617 for (Uri uri : uris) { 618 final ProviderInfo info = pm.resolveContentProvider( 619 uri.getAuthority(), PackageManager.GET_META_DATA); 620 if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) { 621 result.add(uri); 622 } 623 } 624 625 return result.toArray(new Uri[result.size()]); 626 } 627 628 /** 629 * Return thumbnail representing the document at the given Uri. Callers are 630 * responsible for their own in-memory caching. 631 * 632 * @param documentUri document to return thumbnail for, which must have 633 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. 634 * @param size optimal thumbnail size desired. A provider may return a 635 * thumbnail of a different size, but never more than double the 636 * requested size. 637 * @param signal signal used to indicate that caller is no longer interested 638 * in the thumbnail. 639 * @return decoded thumbnail, or {@code null} if problem was encountered. 640 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 641 * android.os.CancellationSignal) 642 */ 643 public static Bitmap getDocumentThumbnail( 644 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 645 final Bundle openOpts = new Bundle(); 646 openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); 647 648 AssetFileDescriptor afd = null; 649 try { 650 afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); 651 652 final FileDescriptor fd = afd.getFileDescriptor(); 653 final long offset = afd.getStartOffset(); 654 final long length = afd.getDeclaredLength(); 655 656 // Some thumbnails might be a region inside a larger file, such as 657 // an EXIF thumbnail. Since BitmapFactory aggressively seeks around 658 // the entire file, we read the region manually. 659 byte[] region = null; 660 if (offset > 0 && length <= 64 * KB_IN_BYTES) { 661 region = new byte[(int) length]; 662 Libcore.os.lseek(fd, offset, SEEK_SET); 663 if (IoBridge.read(fd, region, 0, region.length) != region.length) { 664 region = null; 665 } 666 } 667 668 // We requested a rough thumbnail size, but the remote size may have 669 // returned something giant, so defensively scale down as needed. 670 final BitmapFactory.Options opts = new BitmapFactory.Options(); 671 opts.inJustDecodeBounds = true; 672 if (region != null) { 673 BitmapFactory.decodeByteArray(region, 0, region.length, opts); 674 } else { 675 BitmapFactory.decodeFileDescriptor(fd, null, opts); 676 } 677 678 final int widthSample = opts.outWidth / size.x; 679 final int heightSample = opts.outHeight / size.y; 680 681 opts.inJustDecodeBounds = false; 682 opts.inSampleSize = Math.min(widthSample, heightSample); 683 Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); 684 if (region != null) { 685 return BitmapFactory.decodeByteArray(region, 0, region.length, opts); 686 } else { 687 return BitmapFactory.decodeFileDescriptor(fd, null, opts); 688 } 689 } catch (ErrnoException e) { 690 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 691 return null; 692 } catch (IOException e) { 693 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 694 return null; 695 } finally { 696 IoUtils.closeQuietly(afd); 697 } 698 } 699 700 /** 701 * Create a new document with given MIME type and display name. 702 * 703 * @param parentDocumentUri directory with 704 * {@link Document#FLAG_DIR_SUPPORTS_CREATE} 705 * @param mimeType MIME type of new document 706 * @param displayName name of new document 707 * @return newly created document, or {@code null} if failed 708 * @hide 709 */ 710 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, 711 String mimeType, String displayName) { 712 final Bundle in = new Bundle(); 713 in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); 714 in.putString(Document.COLUMN_MIME_TYPE, mimeType); 715 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 716 717 try { 718 final Bundle out = resolver.call(parentDocumentUri, METHOD_CREATE_DOCUMENT, null, in); 719 return buildDocumentUri( 720 parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); 721 } catch (Exception e) { 722 Log.w(TAG, "Failed to create document", e); 723 return null; 724 } 725 } 726 727 /** 728 * Delete the given document. 729 * 730 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} 731 */ 732 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 733 final Bundle in = new Bundle(); 734 in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); 735 736 try { 737 final Bundle out = resolver.call(documentUri, METHOD_DELETE_DOCUMENT, null, in); 738 return true; 739 } catch (Exception e) { 740 Log.w(TAG, "Failed to delete document", e); 741 return false; 742 } 743 } 744} 745