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