DocumentsContract.java revision 3e1189b3590aefb65a2af720ae2ba959bbd4188d
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 /** 263 * Constants related to a root of documents, including {@link Cursor} 264 * columns names and flags. 265 * <p> 266 * All columns are <em>read-only</em> to client applications. 267 */ 268 public final static class Root { 269 private Root() { 270 } 271 272 /** 273 * Unique ID of a root. This ID is both provided by and interpreted by a 274 * {@link DocumentsProvider}, and should be treated as an opaque value 275 * by client applications. 276 * <p> 277 * Type: STRING 278 */ 279 public static final String COLUMN_ROOT_ID = "root_id"; 280 281 /** 282 * Type of a root, used for clustering when presenting multiple roots to 283 * a user. 284 * <p> 285 * Type: INTEGER (int) 286 * 287 * @see #ROOT_TYPE_SERVICE 288 * @see #ROOT_TYPE_SHORTCUT 289 * @see #ROOT_TYPE_DEVICE 290 */ 291 public static final String COLUMN_ROOT_TYPE = "root_type"; 292 293 /** 294 * Flags that apply to a root. 295 * <p> 296 * Type: INTEGER (int) 297 * 298 * @see #FLAG_ADVANCED 299 * @see #FLAG_EMPTY 300 * @see #FLAG_LOCAL_ONLY 301 * @see #FLAG_SUPPORTS_CREATE 302 * @see #FLAG_SUPPORTS_RECENTS 303 * @see #FLAG_SUPPORTS_SEARCH 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 * Flag indicating that this root supports search. 420 * 421 * @see #COLUMN_FLAGS 422 * @see DocumentsProvider#querySearchDocuments(String, String, 423 * String[]) 424 */ 425 public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; 426 427 /** 428 * Flag indicating that this root is currently empty. This may be used 429 * to hide the root when opening documents, but the root will still be 430 * shown when creating documents and {@link #FLAG_SUPPORTS_CREATE} is 431 * also set. 432 * 433 * @see #COLUMN_FLAGS 434 * @see DocumentsProvider#querySearchDocuments(String, String, 435 * String[]) 436 */ 437 public static final int FLAG_EMPTY = 1 << 5; 438 } 439 440 /** 441 * Optional boolean flag included in a directory {@link Cursor#getExtras()} 442 * indicating that a document provider is still loading data. For example, a 443 * provider has returned some results, but is still waiting on an 444 * outstanding network request. The provider must send a content changed 445 * notification when loading is finished. 446 * 447 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, 448 * boolean) 449 */ 450 public static final String EXTRA_LOADING = "loading"; 451 452 /** 453 * Optional string included in a directory {@link Cursor#getExtras()} 454 * providing an informational message that should be shown to a user. For 455 * example, a provider may wish to indicate that not all documents are 456 * available. 457 */ 458 public static final String EXTRA_INFO = "info"; 459 460 /** 461 * Optional string included in a directory {@link Cursor#getExtras()} 462 * providing an error message that should be shown to a user. For example, a 463 * provider may wish to indicate that a network error occurred. The user may 464 * choose to retry, resulting in a new query. 465 */ 466 public static final String EXTRA_ERROR = "error"; 467 468 /** {@hide} */ 469 public static final String METHOD_CREATE_DOCUMENT = "android:createDocument"; 470 /** {@hide} */ 471 public static final String METHOD_DELETE_DOCUMENT = "android:deleteDocument"; 472 473 /** {@hide} */ 474 public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; 475 476 private static final String PATH_ROOT = "root"; 477 private static final String PATH_RECENT = "recent"; 478 private static final String PATH_DOCUMENT = "document"; 479 private static final String PATH_CHILDREN = "children"; 480 private static final String PATH_SEARCH = "search"; 481 482 private static final String PARAM_QUERY = "query"; 483 private static final String PARAM_MANAGE = "manage"; 484 485 /** 486 * Build Uri representing the roots of a document provider. When queried, a 487 * provider will return one or more rows with columns defined by 488 * {@link Root}. 489 * 490 * @see DocumentsProvider#queryRoots(String[]) 491 */ 492 public static Uri buildRootsUri(String authority) { 493 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 494 .authority(authority).appendPath(PATH_ROOT).build(); 495 } 496 497 /** 498 * Build Uri representing the given {@link Root#COLUMN_ROOT_ID} in a 499 * document provider. 500 * 501 * @see #getRootId(Uri) 502 */ 503 public static Uri buildRootUri(String authority, String rootId) { 504 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 505 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId).build(); 506 } 507 508 /** 509 * Build Uri representing the recently modified documents of a specific root 510 * in a document provider. When queried, a provider will return zero or more 511 * rows with columns defined by {@link Document}. 512 * 513 * @see DocumentsProvider#queryRecentDocuments(String, String[]) 514 * @see #getRootId(Uri) 515 */ 516 public static Uri buildRecentDocumentsUri(String authority, String rootId) { 517 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 518 .authority(authority).appendPath(PATH_ROOT).appendPath(rootId) 519 .appendPath(PATH_RECENT).build(); 520 } 521 522 /** 523 * Build Uri representing the given {@link Document#COLUMN_DOCUMENT_ID} in a 524 * document provider. When queried, a provider will return a single row with 525 * columns defined by {@link Document}. 526 * 527 * @see DocumentsProvider#queryDocument(String, String[]) 528 * @see #getDocumentId(Uri) 529 */ 530 public static Uri buildDocumentUri(String authority, String documentId) { 531 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 532 .authority(authority).appendPath(PATH_DOCUMENT).appendPath(documentId).build(); 533 } 534 535 /** 536 * Build Uri representing the children of the given directory in a document 537 * provider. When queried, a provider will return zero or more rows with 538 * columns defined by {@link Document}. 539 * 540 * @param parentDocumentId the document to return children for, which must 541 * be a directory with MIME type of 542 * {@link Document#MIME_TYPE_DIR}. 543 * @see DocumentsProvider#queryChildDocuments(String, String[], String) 544 * @see #getDocumentId(Uri) 545 */ 546 public static Uri buildChildDocumentsUri(String authority, String parentDocumentId) { 547 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 548 .appendPath(PATH_DOCUMENT).appendPath(parentDocumentId).appendPath(PATH_CHILDREN) 549 .build(); 550 } 551 552 /** 553 * Build Uri representing a search for matching documents under a specific 554 * root in a document provider. When queried, a provider will return zero or 555 * more rows with columns defined by {@link Document}. 556 * 557 * @see DocumentsProvider#querySearchDocuments(String, String, String[]) 558 * @see #getRootId(Uri) 559 * @see #getSearchDocumentsQuery(Uri) 560 */ 561 public static Uri buildSearchDocumentsUri( 562 String authority, String rootId, String query) { 563 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 564 .appendPath(PATH_ROOT).appendPath(rootId).appendPath(PATH_SEARCH) 565 .appendQueryParameter(PARAM_QUERY, query).build(); 566 } 567 568 /** 569 * Extract the {@link Root#COLUMN_ROOT_ID} from the given Uri. 570 */ 571 public static String getRootId(Uri rootUri) { 572 final List<String> paths = rootUri.getPathSegments(); 573 if (paths.size() < 2) { 574 throw new IllegalArgumentException("Not a root: " + rootUri); 575 } 576 if (!PATH_ROOT.equals(paths.get(0))) { 577 throw new IllegalArgumentException("Not a root: " + rootUri); 578 } 579 return paths.get(1); 580 } 581 582 /** 583 * Extract the {@link Document#COLUMN_DOCUMENT_ID} from the given Uri. 584 */ 585 public static String getDocumentId(Uri documentUri) { 586 final List<String> paths = documentUri.getPathSegments(); 587 if (paths.size() < 2) { 588 throw new IllegalArgumentException("Not a document: " + documentUri); 589 } 590 if (!PATH_DOCUMENT.equals(paths.get(0))) { 591 throw new IllegalArgumentException("Not a document: " + documentUri); 592 } 593 return paths.get(1); 594 } 595 596 /** 597 * Extract the search query from a Uri built by 598 * {@link #buildSearchDocumentsUri(String, String, String)}. 599 */ 600 public static String getSearchDocumentsQuery(Uri searchDocumentsUri) { 601 return searchDocumentsUri.getQueryParameter(PARAM_QUERY); 602 } 603 604 /** {@hide} */ 605 public static Uri setManageMode(Uri uri) { 606 return uri.buildUpon().appendQueryParameter(PARAM_MANAGE, "true").build(); 607 } 608 609 /** {@hide} */ 610 public static boolean isManageMode(Uri uri) { 611 return uri.getBooleanQueryParameter(PARAM_MANAGE, false); 612 } 613 614 /** 615 * Return list of all documents that the calling package has "open." These 616 * are Uris matching {@link DocumentsContract} to which persistent 617 * read/write access has been granted, usually through 618 * {@link Intent#ACTION_OPEN_DOCUMENT} or 619 * {@link Intent#ACTION_CREATE_DOCUMENT}. 620 * 621 * @see Context#grantUriPermission(String, Uri, int) 622 * @see Context#revokeUriPermission(Uri, int) 623 * @see ContentResolver#getIncomingUriPermissionGrants(int, int) 624 */ 625 public static Uri[] getOpenDocuments(Context context) { 626 final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION 627 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION; 628 final Uri[] uris = context.getContentResolver() 629 .getIncomingUriPermissionGrants(openedFlags, openedFlags); 630 631 // Filter to only include document providers 632 final PackageManager pm = context.getPackageManager(); 633 final List<Uri> result = Lists.newArrayList(); 634 for (Uri uri : uris) { 635 final ProviderInfo info = pm.resolveContentProvider( 636 uri.getAuthority(), PackageManager.GET_META_DATA); 637 if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) { 638 result.add(uri); 639 } 640 } 641 642 return result.toArray(new Uri[result.size()]); 643 } 644 645 /** 646 * Return thumbnail representing the document at the given Uri. Callers are 647 * responsible for their own in-memory caching. 648 * 649 * @param documentUri document to return thumbnail for, which must have 650 * {@link Document#FLAG_SUPPORTS_THUMBNAIL} set. 651 * @param size optimal thumbnail size desired. A provider may return a 652 * thumbnail of a different size, but never more than double the 653 * requested size. 654 * @param signal signal used to indicate that caller is no longer interested 655 * in the thumbnail. 656 * @return decoded thumbnail, or {@code null} if problem was encountered. 657 * @see DocumentsProvider#openDocumentThumbnail(String, Point, 658 * android.os.CancellationSignal) 659 */ 660 public static Bitmap getDocumentThumbnail( 661 ContentResolver resolver, Uri documentUri, Point size, CancellationSignal signal) { 662 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 663 documentUri.getAuthority()); 664 try { 665 return getDocumentThumbnail(client, documentUri, size, signal); 666 } catch (RemoteException e) { 667 return null; 668 } finally { 669 ContentProviderClient.closeQuietly(client); 670 } 671 } 672 673 /** {@hide} */ 674 public static Bitmap getDocumentThumbnail( 675 ContentProviderClient client, Uri documentUri, Point size, CancellationSignal signal) 676 throws RemoteException { 677 final Bundle openOpts = new Bundle(); 678 openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); 679 680 AssetFileDescriptor afd = null; 681 try { 682 afd = client.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts, signal); 683 684 final FileDescriptor fd = afd.getFileDescriptor(); 685 final long offset = afd.getStartOffset(); 686 687 // Try seeking on the returned FD, since it gives us the most 688 // optimal decode path; otherwise fall back to buffering. 689 BufferedInputStream is = null; 690 try { 691 Libcore.os.lseek(fd, offset, SEEK_SET); 692 } catch (ErrnoException e) { 693 is = new BufferedInputStream(new FileInputStream(fd), THUMBNAIL_BUFFER_SIZE); 694 is.mark(THUMBNAIL_BUFFER_SIZE); 695 } 696 697 // We requested a rough thumbnail size, but the remote size may have 698 // returned something giant, so defensively scale down as needed. 699 final BitmapFactory.Options opts = new BitmapFactory.Options(); 700 opts.inJustDecodeBounds = true; 701 if (is != null) { 702 BitmapFactory.decodeStream(is, null, opts); 703 } else { 704 BitmapFactory.decodeFileDescriptor(fd, null, opts); 705 } 706 707 final int widthSample = opts.outWidth / size.x; 708 final int heightSample = opts.outHeight / size.y; 709 710 opts.inJustDecodeBounds = false; 711 opts.inSampleSize = Math.min(widthSample, heightSample); 712 Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); 713 if (is != null) { 714 is.reset(); 715 return BitmapFactory.decodeStream(is, null, opts); 716 } else { 717 try { 718 Libcore.os.lseek(fd, offset, SEEK_SET); 719 } catch (ErrnoException e) { 720 e.rethrowAsIOException(); 721 } 722 return BitmapFactory.decodeFileDescriptor(fd, null, opts); 723 } 724 } catch (IOException e) { 725 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 726 return null; 727 } finally { 728 IoUtils.closeQuietly(afd); 729 } 730 } 731 732 /** 733 * Create a new document with given MIME type and display name. 734 * 735 * @param parentDocumentUri directory with 736 * {@link Document#FLAG_DIR_SUPPORTS_CREATE} 737 * @param mimeType MIME type of new document 738 * @param displayName name of new document 739 * @return newly created document, or {@code null} if failed 740 * @hide 741 */ 742 public static Uri createDocument(ContentResolver resolver, Uri parentDocumentUri, 743 String mimeType, String displayName) { 744 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 745 parentDocumentUri.getAuthority()); 746 try { 747 return createDocument(client, parentDocumentUri, mimeType, displayName); 748 } finally { 749 ContentProviderClient.closeQuietly(client); 750 } 751 } 752 753 /** {@hide} */ 754 public static Uri createDocument(ContentProviderClient client, Uri parentDocumentUri, 755 String mimeType, String displayName) { 756 final Bundle in = new Bundle(); 757 in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(parentDocumentUri)); 758 in.putString(Document.COLUMN_MIME_TYPE, mimeType); 759 in.putString(Document.COLUMN_DISPLAY_NAME, displayName); 760 761 try { 762 final Bundle out = client.call(METHOD_CREATE_DOCUMENT, null, in); 763 return buildDocumentUri( 764 parentDocumentUri.getAuthority(), out.getString(Document.COLUMN_DOCUMENT_ID)); 765 } catch (Exception e) { 766 Log.w(TAG, "Failed to create document", e); 767 return null; 768 } 769 } 770 771 /** 772 * Delete the given document. 773 * 774 * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE} 775 */ 776 public static boolean deleteDocument(ContentResolver resolver, Uri documentUri) { 777 final ContentProviderClient client = resolver.acquireUnstableContentProviderClient( 778 documentUri.getAuthority()); 779 try { 780 return deleteDocument(client, documentUri); 781 } finally { 782 ContentProviderClient.closeQuietly(client); 783 } 784 } 785 786 /** {@hide} */ 787 public static boolean deleteDocument(ContentProviderClient client, Uri documentUri) { 788 final Bundle in = new Bundle(); 789 in.putString(Document.COLUMN_DOCUMENT_ID, getDocumentId(documentUri)); 790 791 try { 792 final Bundle out = client.call(METHOD_DELETE_DOCUMENT, null, in); 793 return true; 794 } catch (Exception e) { 795 Log.w(TAG, "Failed to delete document", e); 796 return false; 797 } 798 } 799} 800