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