DocumentsContract.java revision 6398343e83b3fd11dd6536cf6f390a52c1e19d2e
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.ContentProvider; 23import android.content.ContentResolver; 24import android.content.ContentValues; 25import android.content.Context; 26import android.content.Intent; 27import android.content.pm.PackageManager; 28import android.content.pm.ProviderInfo; 29import android.content.res.AssetFileDescriptor; 30import android.database.Cursor; 31import android.graphics.Bitmap; 32import android.graphics.BitmapFactory; 33import android.graphics.Point; 34import android.net.Uri; 35import android.os.Bundle; 36import android.os.ParcelFileDescriptor; 37import android.os.ParcelFileDescriptor.OnCloseListener; 38import android.util.Log; 39 40import com.google.android.collect.Lists; 41 42import libcore.io.ErrnoException; 43import libcore.io.IoBridge; 44import libcore.io.IoUtils; 45import libcore.io.Libcore; 46 47import java.io.FileDescriptor; 48import java.io.IOException; 49import java.util.List; 50 51/** 52 * Defines the contract between a documents provider and the platform. 53 * <p> 54 * A document provider is a {@link ContentProvider} that presents a set of 55 * documents in a hierarchical structure. The system provides UI that visualizes 56 * all available document providers, offering users the ability to open existing 57 * documents or create new documents. 58 * <p> 59 * Each provider expresses one or more "roots" which each serve as the top-level 60 * of a tree. For example, a root could represent an account, or a physical 61 * storage device. Under each root, documents are referenced by a unique 62 * {@link DocumentColumns#DOC_ID}, and each root starts at the 63 * {@link Documents#DOC_ID_ROOT} document. 64 * <p> 65 * Documents can be either an openable file (with a specific MIME type), or a 66 * directory containing additional documents (with the 67 * {@link Documents#MIME_TYPE_DIR} MIME type). Each document can have different 68 * capabilities, as described by {@link DocumentColumns#FLAGS}. The same 69 * {@link DocumentColumns#DOC_ID} can be included in multiple directories. 70 * <p> 71 * Document providers must be protected with the 72 * {@link android.Manifest.permission#MANAGE_DOCUMENTS} permission, which can 73 * only be requested by the system. The system-provided UI then issues narrow 74 * Uri permission grants for individual documents when the user explicitly picks 75 * documents. 76 * 77 * @see Intent#ACTION_OPEN_DOCUMENT 78 * @see Intent#ACTION_CREATE_DOCUMENT 79 */ 80public final class DocumentsContract { 81 private static final String TAG = "Documents"; 82 83 // content://com.example/roots/ 84 // content://com.example/roots/sdcard/ 85 // content://com.example/roots/sdcard/docs/0/ 86 // content://com.example/roots/sdcard/docs/0/contents/ 87 // content://com.example/roots/sdcard/docs/0/search/?query=pony 88 89 /** {@hide} */ 90 public static final String META_DATA_DOCUMENT_PROVIDER = "android.content.DOCUMENT_PROVIDER"; 91 92 /** {@hide} */ 93 public static final String ACTION_DOCUMENT_CHANGED = "android.provider.action.DOCUMENT_CHANGED"; 94 95 /** 96 * Constants for individual documents. 97 */ 98 public static class Documents { 99 private Documents() { 100 } 101 102 /** 103 * MIME type of a document which is a directory that may contain additional 104 * documents. 105 * 106 * @see #buildContentsUri(String, String, String) 107 */ 108 public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/doc"; 109 110 /** 111 * {@link DocumentColumns#DOC_ID} value representing the root directory of a 112 * documents root. 113 */ 114 public static final String DOC_ID_ROOT = "0"; 115 116 /** 117 * Flag indicating that a document is a directory that supports creation of 118 * new files within it. 119 * 120 * @see DocumentColumns#FLAGS 121 * @see #createDocument(ContentResolver, Uri, String, String) 122 */ 123 public static final int FLAG_SUPPORTS_CREATE = 1; 124 125 /** 126 * Flag indicating that a document is renamable. 127 * 128 * @see DocumentColumns#FLAGS 129 * @see #renameDocument(ContentResolver, Uri, String) 130 */ 131 public static final int FLAG_SUPPORTS_RENAME = 1 << 1; 132 133 /** 134 * Flag indicating that a document is deletable. 135 * 136 * @see DocumentColumns#FLAGS 137 */ 138 public static final int FLAG_SUPPORTS_DELETE = 1 << 2; 139 140 /** 141 * Flag indicating that a document can be represented as a thumbnail. 142 * 143 * @see DocumentColumns#FLAGS 144 * @see #getThumbnail(ContentResolver, Uri, Point) 145 */ 146 public static final int FLAG_SUPPORTS_THUMBNAIL = 1 << 3; 147 148 /** 149 * Flag indicating that a document is a directory that supports search. 150 * 151 * @see DocumentColumns#FLAGS 152 */ 153 public static final int FLAG_SUPPORTS_SEARCH = 1 << 4; 154 155 /** 156 * Flag indicating that a document is writable. 157 * 158 * @see DocumentColumns#FLAGS 159 */ 160 public static final int FLAG_SUPPORTS_WRITE = 1 << 5; 161 162 /** 163 * Flag indicating that a document is a directory that prefers its contents 164 * be shown in a larger format grid. Usually suitable when a directory 165 * contains mostly pictures. 166 * 167 * @see DocumentColumns#FLAGS 168 */ 169 public static final int FLAG_PREFERS_GRID = 1 << 6; 170 } 171 172 /** 173 * Optimal dimensions for a document thumbnail request, stored as a 174 * {@link Point} object. This is only a hint, and the returned thumbnail may 175 * have different dimensions. 176 * 177 * @see ContentProvider#openTypedAssetFile(Uri, String, Bundle) 178 */ 179 public static final String EXTRA_THUMBNAIL_SIZE = "thumbnail_size"; 180 181 /** 182 * Extra boolean flag included in a directory {@link Cursor#getExtras()} 183 * indicating that the document provider can provide additional data if 184 * requested, such as additional search results. 185 */ 186 public static final String EXTRA_HAS_MORE = "has_more"; 187 188 /** 189 * Extra boolean flag included in a {@link Cursor#respond(Bundle)} call to a 190 * directory to request that additional data should be fetched. When 191 * requested data is ready, the provider should send a change notification 192 * to cause a requery. 193 * 194 * @see Cursor#respond(Bundle) 195 * @see ContentResolver#notifyChange(Uri, android.database.ContentObserver, 196 * boolean) 197 */ 198 public static final String EXTRA_REQUEST_MORE = "request_more"; 199 200 private static final String PATH_ROOTS = "roots"; 201 private static final String PATH_DOCS = "docs"; 202 private static final String PATH_CONTENTS = "contents"; 203 private static final String PATH_SEARCH = "search"; 204 205 private static final String PARAM_QUERY = "query"; 206 private static final String PARAM_LOCAL_ONLY = "localOnly"; 207 208 /** 209 * Build Uri representing the roots offered by a document provider. 210 */ 211 public static Uri buildRootsUri(String authority) { 212 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 213 .authority(authority).appendPath(PATH_ROOTS).build(); 214 } 215 216 /** 217 * Build Uri representing a specific root offered by a document provider. 218 */ 219 public static Uri buildRootUri(String authority, String rootId) { 220 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 221 .authority(authority).appendPath(PATH_ROOTS).appendPath(rootId).build(); 222 } 223 224 /** 225 * Build Uri representing the given {@link DocumentColumns#DOC_ID} in a 226 * document provider. 227 */ 228 public static Uri buildDocumentUri(String authority, String rootId, String docId) { 229 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 230 .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId) 231 .build(); 232 } 233 234 /** 235 * Build Uri representing the contents of the given directory in a document 236 * provider. The given document must be {@link Documents#MIME_TYPE_DIR}. 237 */ 238 public static Uri buildContentsUri(String authority, String rootId, String docId) { 239 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 240 .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId) 241 .appendPath(PATH_CONTENTS).build(); 242 } 243 244 /** 245 * Build Uri representing a search for matching documents under a specific 246 * directory in a document provider. The given document must have 247 * {@link Documents#FLAG_SUPPORTS_SEARCH}. 248 */ 249 public static Uri buildSearchUri(String authority, String rootId, String docId, String query) { 250 return new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT).authority(authority) 251 .appendPath(PATH_ROOTS).appendPath(rootId).appendPath(PATH_DOCS).appendPath(docId) 252 .appendPath(PATH_SEARCH).appendQueryParameter(PARAM_QUERY, query).build(); 253 } 254 255 /** 256 * Convenience method for {@link #buildDocumentUri(String, String, String)}, 257 * extracting authority and root from the given Uri. 258 */ 259 public static Uri buildDocumentUri(Uri relatedUri, String docId) { 260 return buildDocumentUri(relatedUri.getAuthority(), getRootId(relatedUri), docId); 261 } 262 263 /** 264 * Convenience method for {@link #buildContentsUri(String, String, String)}, 265 * extracting authority and root from the given Uri. 266 */ 267 public static Uri buildContentsUri(Uri relatedUri) { 268 return buildContentsUri( 269 relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri)); 270 } 271 272 /** 273 * Convenience method for 274 * {@link #buildSearchUri(String, String, String, String)}, extracting 275 * authority and root from the given Uri. 276 */ 277 public static Uri buildSearchUri(Uri relatedUri, String query) { 278 return buildSearchUri( 279 relatedUri.getAuthority(), getRootId(relatedUri), getDocId(relatedUri), query); 280 } 281 282 /** 283 * Extract the {@link RootColumns#ROOT_ID} from the given Uri. 284 */ 285 public static String getRootId(Uri documentUri) { 286 final List<String> paths = documentUri.getPathSegments(); 287 if (paths.size() < 2) { 288 throw new IllegalArgumentException("Not a root: " + documentUri); 289 } 290 if (!PATH_ROOTS.equals(paths.get(0))) { 291 throw new IllegalArgumentException("Not a root: " + documentUri); 292 } 293 return paths.get(1); 294 } 295 296 /** 297 * Extract the {@link DocumentColumns#DOC_ID} from the given Uri. 298 */ 299 public static String getDocId(Uri documentUri) { 300 final List<String> paths = documentUri.getPathSegments(); 301 if (paths.size() < 4) { 302 throw new IllegalArgumentException("Not a document: " + documentUri); 303 } 304 if (!PATH_ROOTS.equals(paths.get(0))) { 305 throw new IllegalArgumentException("Not a document: " + documentUri); 306 } 307 if (!PATH_DOCS.equals(paths.get(2))) { 308 throw new IllegalArgumentException("Not a document: " + documentUri); 309 } 310 return paths.get(3); 311 } 312 313 /** 314 * Return requested search query from the given Uri, as constructed by 315 * {@link #buildSearchUri(String, String, String, String)}. 316 */ 317 public static String getSearchQuery(Uri documentUri) { 318 return documentUri.getQueryParameter(PARAM_QUERY); 319 } 320 321 /** 322 * Mark the given Uri to indicate that only locally-available data should be 323 * returned. That is, no network connections should be initiated to provide 324 * the metadata or content. 325 */ 326 public static Uri setLocalOnly(Uri documentUri) { 327 return documentUri.buildUpon() 328 .appendQueryParameter(PARAM_LOCAL_ONLY, String.valueOf(true)).build(); 329 } 330 331 /** 332 * Return if the given Uri is requesting that only locally-available data be 333 * returned. That is, no network connections should be initiated to provide 334 * the metadata or content. 335 */ 336 public static boolean isLocalOnly(Uri documentUri) { 337 return documentUri.getBooleanQueryParameter(PARAM_LOCAL_ONLY, false); 338 } 339 340 /** 341 * Standard columns for document queries. Document providers <em>must</em> 342 * support at least these columns when queried. 343 * 344 * @see DocumentsContract#buildDocumentUri(String, String, String) 345 * @see DocumentsContract#buildContentsUri(String, String, String) 346 * @see DocumentsContract#buildSearchUri(String, String, String, String) 347 */ 348 public interface DocumentColumns extends OpenableColumns { 349 /** 350 * The ID for a document under a storage backend root. Values 351 * <em>must</em> never change once returned. This field is read-only to 352 * document clients. 353 * <p> 354 * Type: STRING 355 */ 356 public static final String DOC_ID = "doc_id"; 357 358 /** 359 * MIME type of a document, matching the value returned by 360 * {@link ContentResolver#getType(android.net.Uri)}. This field must be 361 * provided when a new document is created. This field is read-only to 362 * document clients. 363 * <p> 364 * Type: STRING 365 * 366 * @see Documents#MIME_TYPE_DIR 367 */ 368 public static final String MIME_TYPE = "mime_type"; 369 370 /** 371 * Timestamp when a document was last modified, in milliseconds since 372 * January 1, 1970 00:00:00.0 UTC. This field is read-only to document 373 * clients. Document providers can update this field using events from 374 * {@link OnCloseListener} or other reliable 375 * {@link ParcelFileDescriptor} transport. 376 * <p> 377 * Type: INTEGER (long) 378 * 379 * @see System#currentTimeMillis() 380 */ 381 public static final String LAST_MODIFIED = "last_modified"; 382 383 /** 384 * Flags that apply to a specific document. This field is read-only to 385 * document clients. 386 * <p> 387 * Type: INTEGER (int) 388 */ 389 public static final String FLAGS = "flags"; 390 391 /** 392 * Summary for this document, or {@code null} to omit. This field is 393 * read-only to document clients. 394 * <p> 395 * Type: STRING 396 */ 397 public static final String SUMMARY = "summary"; 398 } 399 400 /** 401 * Constants for individual document roots. 402 */ 403 public static class Roots { 404 private Roots() { 405 } 406 407 public static final String MIME_TYPE_DIR = "vnd.android.cursor.dir/root"; 408 public static final String MIME_TYPE_ITEM = "vnd.android.cursor.item/root"; 409 410 /** 411 * Root that represents a storage service, such as a cloud-based 412 * service. 413 * 414 * @see RootColumns#ROOT_TYPE 415 */ 416 public static final int ROOT_TYPE_SERVICE = 1; 417 418 /** 419 * Root that represents a shortcut to content that may be available 420 * elsewhere through another storage root. 421 * 422 * @see RootColumns#ROOT_TYPE 423 */ 424 public static final int ROOT_TYPE_SHORTCUT = 2; 425 426 /** 427 * Root that represents a physical storage device. 428 * 429 * @see RootColumns#ROOT_TYPE 430 */ 431 public static final int ROOT_TYPE_DEVICE = 3; 432 433 /** 434 * Root that represents a physical storage device that should only be 435 * displayed to advanced users. 436 * 437 * @see RootColumns#ROOT_TYPE 438 */ 439 public static final int ROOT_TYPE_DEVICE_ADVANCED = 4; 440 } 441 442 /** 443 * Standard columns for document root queries. 444 * 445 * @see DocumentsContract#buildRootsUri(String) 446 * @see DocumentsContract#buildRootUri(String, String) 447 */ 448 public interface RootColumns { 449 public static final String ROOT_ID = "root_id"; 450 451 /** 452 * Storage root type, use for clustering. This field is read-only to 453 * document clients. 454 * <p> 455 * Type: INTEGER (int) 456 * 457 * @see Roots#ROOT_TYPE_SERVICE 458 * @see Roots#ROOT_TYPE_DEVICE 459 */ 460 public static final String ROOT_TYPE = "root_type"; 461 462 /** 463 * Icon resource ID for this storage root, or {@code null} to use the 464 * default {@link ProviderInfo#icon}. This field is read-only to 465 * document clients. 466 * <p> 467 * Type: INTEGER (int) 468 */ 469 public static final String ICON = "icon"; 470 471 /** 472 * Title for this storage root, or {@code null} to use the default 473 * {@link ProviderInfo#labelRes}. This field is read-only to document 474 * clients. 475 * <p> 476 * Type: STRING 477 */ 478 public static final String TITLE = "title"; 479 480 /** 481 * Summary for this storage root, or {@code null} to omit. This field is 482 * read-only to document clients. 483 * <p> 484 * Type: STRING 485 */ 486 public static final String SUMMARY = "summary"; 487 488 /** 489 * Number of free bytes of available in this storage root, or 490 * {@code null} if unknown or unbounded. This field is read-only to 491 * document clients. 492 * <p> 493 * Type: INTEGER (long) 494 */ 495 public static final String AVAILABLE_BYTES = "available_bytes"; 496 } 497 498 /** 499 * Return list of all documents that the calling package has "open." These 500 * are Uris matching {@link DocumentsContract} to which persistent 501 * read/write access has been granted, usually through 502 * {@link Intent#ACTION_OPEN_DOCUMENT} or 503 * {@link Intent#ACTION_CREATE_DOCUMENT}. 504 * 505 * @see Context#grantUriPermission(String, Uri, int) 506 * @see ContentResolver#getIncomingUriPermissionGrants(int, int) 507 */ 508 public static Uri[] getOpenDocuments(Context context) { 509 final int openedFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION 510 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_PERSIST_GRANT_URI_PERMISSION; 511 final Uri[] uris = context.getContentResolver() 512 .getIncomingUriPermissionGrants(openedFlags, openedFlags); 513 514 // Filter to only include document providers 515 final PackageManager pm = context.getPackageManager(); 516 final List<Uri> result = Lists.newArrayList(); 517 for (Uri uri : uris) { 518 final ProviderInfo info = pm.resolveContentProvider( 519 uri.getAuthority(), PackageManager.GET_META_DATA); 520 if (info.metaData.containsKey(META_DATA_DOCUMENT_PROVIDER)) { 521 result.add(uri); 522 } 523 } 524 525 return result.toArray(new Uri[result.size()]); 526 } 527 528 /** 529 * Return thumbnail representing the document at the given URI. Callers are 530 * responsible for their own in-memory caching. Given document must have 531 * {@link Documents#FLAG_SUPPORTS_THUMBNAIL} set. 532 * 533 * @return decoded thumbnail, or {@code null} if problem was encountered. 534 */ 535 public static Bitmap getThumbnail(ContentResolver resolver, Uri documentUri, Point size) { 536 final Bundle openOpts = new Bundle(); 537 openOpts.putParcelable(DocumentsContract.EXTRA_THUMBNAIL_SIZE, size); 538 539 AssetFileDescriptor afd = null; 540 try { 541 afd = resolver.openTypedAssetFileDescriptor(documentUri, "image/*", openOpts); 542 543 final FileDescriptor fd = afd.getFileDescriptor(); 544 final long offset = afd.getStartOffset(); 545 final long length = afd.getDeclaredLength(); 546 547 // Some thumbnails might be a region inside a larger file, such as 548 // an EXIF thumbnail. Since BitmapFactory aggressively seeks around 549 // the entire file, we read the region manually. 550 byte[] region = null; 551 if (offset > 0 && length <= 64 * KB_IN_BYTES) { 552 region = new byte[(int) length]; 553 Libcore.os.lseek(fd, offset, SEEK_SET); 554 if (IoBridge.read(fd, region, 0, region.length) != region.length) { 555 region = null; 556 } 557 } 558 559 // We requested a rough thumbnail size, but the remote size may have 560 // returned something giant, so defensively scale down as needed. 561 final BitmapFactory.Options opts = new BitmapFactory.Options(); 562 opts.inJustDecodeBounds = true; 563 if (region != null) { 564 BitmapFactory.decodeByteArray(region, 0, region.length, opts); 565 } else { 566 BitmapFactory.decodeFileDescriptor(fd, null, opts); 567 } 568 569 final int widthSample = opts.outWidth / size.x; 570 final int heightSample = opts.outHeight / size.y; 571 572 opts.inJustDecodeBounds = false; 573 opts.inSampleSize = Math.min(widthSample, heightSample); 574 Log.d(TAG, "Decoding with sample size " + opts.inSampleSize); 575 if (region != null) { 576 return BitmapFactory.decodeByteArray(region, 0, region.length, opts); 577 } else { 578 return BitmapFactory.decodeFileDescriptor(fd, null, opts); 579 } 580 } catch (ErrnoException e) { 581 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 582 return null; 583 } catch (IOException e) { 584 Log.w(TAG, "Failed to load thumbnail for " + documentUri + ": " + e); 585 return null; 586 } finally { 587 IoUtils.closeQuietly(afd); 588 } 589 } 590 591 /** 592 * Create a new document under a specific parent document with the given 593 * display name and MIME type. 594 * 595 * @param parentDocumentUri document with 596 * {@link Documents#FLAG_SUPPORTS_CREATE} 597 * @param displayName name for new document 598 * @param mimeType MIME type for new document, which cannot be changed 599 * @return newly created document Uri, or {@code null} if failed 600 */ 601 public static Uri createDocument( 602 ContentResolver resolver, Uri parentDocumentUri, String displayName, String mimeType) { 603 final ContentValues values = new ContentValues(); 604 values.put(DocumentColumns.MIME_TYPE, mimeType); 605 values.put(DocumentColumns.DISPLAY_NAME, displayName); 606 return resolver.insert(parentDocumentUri, values); 607 } 608 609 /** 610 * Rename the document at the given URI. Given document must have 611 * {@link Documents#FLAG_SUPPORTS_RENAME} set. 612 * 613 * @return if rename was successful. 614 */ 615 public static boolean renameDocument( 616 ContentResolver resolver, Uri documentUri, String displayName) { 617 final ContentValues values = new ContentValues(); 618 values.put(DocumentColumns.DISPLAY_NAME, displayName); 619 return (resolver.update(documentUri, values, null, null) == 1); 620 } 621 622 /** 623 * Notify the system that roots have changed for the given storage provider. 624 * This signal is used to invalidate internal caches. 625 */ 626 public static void notifyRootsChanged(Context context, String authority) { 627 final Intent intent = new Intent(ACTION_DOCUMENT_CHANGED); 628 intent.setData(buildRootsUri(authority)); 629 context.sendBroadcast(intent); 630 } 631} 632