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