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