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