1 page.title=Storage Access Framework 2 @jd:body 3 <div id="qv-wrapper"> 4 <div id="qv"> 5 6 <h2>In this document</h2> 7 <ol> 8 <li> 9 <a href="#overview">Overview</a> 10 </li> 11 <li> 12 <a href="#flow">Control Flow</a> 13 </li> 14 <li> 15 <a href="#client">Writing a Client App</a> 16 <ol> 17 <li><a href="#search">Search for documents</a></li> 18 <li><a href="#process">Process results</a></li> 19 <li><a href="#metadata">Examine document metadata</a></li> 20 <li><a href="#open">Open a document</a></li> 21 <li><a href="#create">Create a new document</a></li> 22 <li><a href="#delete">Delete a document</a></li> 23 <li><a href="#edit">Edit a document</a></li> 24 <li><a href="#permissions">Persist permissions</a></li> 25 </ol> 26 </li> 27 <li><a href="#custom">Writing a Custom Document Provider</a> 28 <ol> 29 <li><a href="#manifest">Manifest</a></li> 30 <li><a href="#contract">Contracts</a></li> 31 <li><a href="#subclass">Subclass DocumentsProvider</a></li> 32 <li><a href="#security">Security</a></li> 33 </ol> 34 </li> 35 36 </ol> 37 <h2>Key classes</h2> 38 <ol> 39 <li>{@link android.provider.DocumentsProvider}</li> 40 <li>{@link android.provider.DocumentsContract}</li> 41 <li>{@link android.provider.DocumentsContract.Document}</li> 42 <li>{@link android.provider.DocumentsContract.Root}</li> 43 </ol> 44 45 <h2>See Also</h2> 46 <ol> 47 <li> 48 <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> 49 Content Provider Basics 50 </a> 51 </li> 52 </ol> 53 </div> 54 </div> 55 <p>Android 4.4 (API level 19) introduces the Storage Access Framework. The 56 Storage Access Framework encapsulates capabilities in the Android platform that 57 allow apps to request files from file storage services. The Storage Access 58 Framework includes the following:</p> 59 60 <ul> 61 <li><strong>Document provider</strong>—A content provider that allows a 62 storage service (such as Google Drive) to reveal the files it manages. This is 63 implemented as a subclass of the {@link android.provider.DocumentsProvider} class. 64 The document provider schema is based on a traditional file hierarchy, 65 though how your document provider physically stores data is up to you. 66 The Android platform includes several built-in document providers, such as 67 Downloads, Images, and Videos.</li> 68 69 <li><strong>Client app</strong>—A custom app that invokes the 70 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} and/or 71 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} intent and receives the 72 files returned by document providers.</li> 73 74 <li><strong>Picker</strong>—A system UI that lets users access documents from all 75 document providers that satisfy the client app's search criteria.</li> 76 </ul> 77 78 <p>Some of the features offered by the Storage Access Framework are as follows:</p> 79 <ul> 80 <li>Lets users browse content from all document providers, not just a single app.</li> 81 <li>Makes it possible for your app to have long term, persistent access to 82 documents owned by a document provider. Through this access users can add, edit, 83 save, and delete files on the provider.</li> 84 <li>Supports multiple user accounts and transient roots such as USB storage 85 providers, which only appear if the drive is plugged in. </li> 86 </ul> 87 88 <h2 id ="overview">Overview</h2> 89 90 <p>The Storage Access Framework centers around a content provider that is a 91 subclass of the {@link android.provider.DocumentsProvider} class. Within a <em>document provider</em>, data is 92 structured as a traditional file hierarchy:</p> 93 <p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p> 94 <p class="img-caption"><strong>Figure 1.</strong> Document provider data model. A Root points to a single Document, 95 which then starts the fan-out of the entire tree.</p> 96 97 <p>Note the following:</p> 98 <ul> 99 100 <li>Each document provider reports one or more 101 "roots" which are starting points into exploring a tree of documents. 102 Each root has a unique {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID}, 103 and it points to a document (a directory) 104 representing the contents under that root. 105 Roots are dynamic by design to support use cases like multiple accounts, 106 transient USB storage devices, or user login/log out.</li> 107 108 <li>Under each root is a single document. That document points to 1 to <em>N</em> documents, 109 each of which in turn can point to 1 to <em>N</em> documents. </li> 110 111 <li>Each storage backend surfaces 112 individual files and directories by referencing them with a unique 113 {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}. 114 Document IDs must be unique and not change once issued, since they are used for persistent 115 URI grants across device reboots.</li> 116 117 118 <li>Documents can be either an openable file (with a specific MIME type), or a 119 directory containing additional documents (with the 120 {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME type).</li> 121 122 <li>Each document can have different capabilities, as described by 123 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}. 124 For example, {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, 125 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE}, and 126 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. 127 The same {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} can be 128 included in multiple directories.</li> 129 </ul> 130 131 <h2 id="flow">Control Flow</h2> 132 <p>As stated above, the document provider data model is based on a traditional 133 file hierarchy. However, you can physically store your data however you like, as 134 long as it can be accessed through the {@link android.provider.DocumentsProvider} API. For example, you 135 could use tag-based cloud storage for your data.</p> 136 137 <p>Figure 2 shows an example of how a photo app might use the Storage Access Framework 138 to access stored data:</p> 139 <p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p> 140 141 <p class="img-caption"><strong>Figure 2.</strong> Storage Access Framework Flow</p> 142 143 <p>Note the following:</p> 144 <ul> 145 146 <li>In the Storage Access Framework, providers and clients don't interact 147 directly. A client requests permission to interact 148 with files (that is, to read, edit, create, or delete files).</li> 149 150 <li>The interaction starts when an application (in this example, a photo app) fires the intent 151 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} or {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. The intent may include filters 152 to further refine the criteria—for example, "give me all openable files 153 that have the 'image' MIME type."</li> 154 155 <li>Once the intent fires, the system picker goes to each registered provider 156 and shows the user the matching content roots.</li> 157 158 <li>The picker gives users a standard interface for accessing documents, even 159 though the underlying document providers may be very different. For example, figure 2 160 shows a Google Drive provider, a USB provider, and a cloud provider.</li> 161 </ul> 162 163 <p>Figure 3 shows a picker in which a user searching for images has selected a 164 Google Drive account:</p> 165 166 <p><img src="{@docRoot}images/providers/storage_picker.png" width="340" 167 alt="picker" style="border:2px solid #ddd"/></p> 168 169 <p class="img-caption"><strong>Figure 3.</strong> Picker</p> 170 171 <p>When the user selects Google Drive the images are displayed, as shown in 172 figure 4. From that point on, the user can interact with them in whatever ways 173 are supported by the provider and client app. 174 175 <p><img src="{@docRoot}images/providers/storage_photos.png" width="340" 176 alt="picker" style="border:2px solid #ddd"/></p> 177 178 <p class="img-caption"><strong>Figure 4.</strong> Images</p> 179 180 <h2 id="client">Writing a Client App</h2> 181 182 <p>On Android 4.3 and lower, if you want your app to retrieve a file from another 183 app, it must invoke an intent such as {@link android.content.Intent#ACTION_PICK} 184 or {@link android.content.Intent#ACTION_GET_CONTENT}. The user must then select 185 a single app from which to pick a file and the selected app must provide a user 186 interface for the user to browse and pick from the available files. </p> 187 188 <p>On Android 4.4 and higher, you have the additional option of using the 189 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent, 190 which displays a picker UI controlled by the system that allows the user to 191 browse all files that other apps have made available. From this single UI, the 192 user can pick a file from any of the supported apps.</p> 193 194 <p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} is 195 not intended to be a replacement for {@link android.content.Intent#ACTION_GET_CONTENT}. 196 The one you should use depends on the needs of your app:</p> 197 198 <ul> 199 <li>Use {@link android.content.Intent#ACTION_GET_CONTENT} if you want your app 200 to simply read/import data. With this approach, the app imports a copy of the data, 201 such as an image file.</li> 202 203 <li>Use {@link android.content.Intent#ACTION_OPEN_DOCUMENT} if you want your 204 app to have long term, persistent access to documents owned by a document 205 provider. An example would be a photo-editing app that lets users edit 206 images stored in a document provider. </li> 207 208 </ul> 209 210 211 <p>This section describes how to write client apps based on the 212 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} and 213 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} intents.</p> 214 215 216 <h3 id="search">Search for documents</h3> 217 218 <p> 219 The following snippet uses {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 220 to search for document providers that 221 contain image files:</p> 222 223 <pre>private static final int READ_REQUEST_CODE = 42; 224 ... 225 /** 226 * Fires an intent to spin up the "file chooser" UI and select an image. 227 */ 228 public void performFileSearch() { 229 230 // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file 231 // browser. 232 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 233 234 // Filter to only show results that can be "opened", such as a 235 // file (as opposed to a list of contacts or timezones) 236 intent.addCategory(Intent.CATEGORY_OPENABLE); 237 238 // Filter to show only images, using the image MIME data type. 239 // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". 240 // To search for all documents available via installed storage providers, 241 // it would be "*/*". 242 intent.setType("image/*"); 243 244 startActivityForResult(intent, READ_REQUEST_CODE); 245 }</pre> 246 247 <p>Note the following:</p> 248 <ul> 249 <li>When the app fires the {@link android.content.Intent#ACTION_OPEN_DOCUMENT} 250 intent, it launches a picker that displays all matching document providers.</li> 251 252 <li>Adding the category {@link android.content.Intent#CATEGORY_OPENABLE} to the 253 intent filters the results to display only documents that can be opened, such as image files.</li> 254 255 <li>The statement {@code intent.setType("image/*")} further filters to 256 display only documents that have the image MIME data type.</li> 257 </ul> 258 259 <h3 id="results">Process Results</h3> 260 261 <p>Once the user selects a document in the picker, 262 {@link android.app.Activity#onActivityResult onActivityResult()} gets called. 263 The URI that points to the selected document is contained in the {@code resultData} 264 parameter. Extract the URI using {@link android.content.Intent#getData getData()}. 265 Once you have it, you can use it to retrieve the document the user wants. For 266 example:</p> 267 268 <pre>@Override 269 public void onActivityResult(int requestCode, int resultCode, 270 Intent resultData) { 271 272 // The ACTION_OPEN_DOCUMENT intent was sent with the request code 273 // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the 274 // response to some other intent, and the code below shouldn't run at all. 275 276 if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { 277 // The document selected by the user won't be returned in the intent. 278 // Instead, a URI to that document will be contained in the return intent 279 // provided to this method as a parameter. 280 // Pull that URI using resultData.getData(). 281 Uri uri = null; 282 if (resultData != null) { 283 uri = resultData.getData(); 284 Log.i(TAG, "Uri: " + uri.toString()); 285 showImage(uri); 286 } 287 } 288 } 289 </pre> 290 291 <h3 id="metadata">Examine document metadata</h3> 292 293 <p>Once you have the URI for a document, you gain access to its metadata. This 294 snippet grabs the metadata for a document specified by the URI, and logs it:</p> 295 296 <pre>public void dumpImageMetaData(Uri uri) { 297 298 // The query, since it only applies to a single document, will only return 299 // one row. There's no need to filter, sort, or select fields, since we want 300 // all fields for one document. 301 Cursor cursor = getActivity().getContentResolver() 302 .query(uri, null, null, null, null, null); 303 304 try { 305 // moveToFirst() returns false if the cursor has 0 rows. Very handy for 306 // "if there's anything to look at, look at it" conditionals. 307 if (cursor != null && cursor.moveToFirst()) { 308 309 // Note it's called "Display Name". This is 310 // provider-specific, and might not necessarily be the file name. 311 String displayName = cursor.getString( 312 cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); 313 Log.i(TAG, "Display Name: " + displayName); 314 315 int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); 316 // If the size is unknown, the value stored is null. But since an 317 // int can't be null in Java, the behavior is implementation-specific, 318 // which is just a fancy term for "unpredictable". So as 319 // a rule, check if it's null before assigning to an int. This will 320 // happen often: The storage API allows for remote files, whose 321 // size might not be locally known. 322 String size = null; 323 if (!cursor.isNull(sizeIndex)) { 324 // Technically the column stores an int, but cursor.getString() 325 // will do the conversion automatically. 326 size = cursor.getString(sizeIndex); 327 } else { 328 size = "Unknown"; 329 } 330 Log.i(TAG, "Size: " + size); 331 } 332 } finally { 333 cursor.close(); 334 } 335 } 336 </pre> 337 338 <h3 id="open-client">Open a document</h3> 339 340 <p>Once you have the URI for a document, you can open it or do whatever else 341 you want to do with it.</p> 342 343 <h4>Bitmap</h4> 344 345 <p>Here is an example of how you might open a {@link android.graphics.Bitmap}:</p> 346 347 <pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException { 348 ParcelFileDescriptor parcelFileDescriptor = 349 getContentResolver().openFileDescriptor(uri, "r"); 350 FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); 351 Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); 352 parcelFileDescriptor.close(); 353 return image; 354 } 355 </pre> 356 357 <p>Note that you should not do this operation on the UI thread. Do it in the 358 background, using {@link android.os.AsyncTask}. Once you open the bitmap, you 359 can display it in an {@link android.widget.ImageView}. 360 </p> 361 362 <h4>Get an InputStream</h4> 363 364 <p>Here is an example of how you can get an {@link java.io.InputStream} from the URI. In this 365 snippet, the lines of the file are being read into a string:</p> 366 367 <pre>private String readTextFromUri(Uri uri) throws IOException { 368 InputStream inputStream = getContentResolver().openInputStream(uri); 369 BufferedReader reader = new BufferedReader(new InputStreamReader( 370 inputStream)); 371 StringBuilder stringBuilder = new StringBuilder(); 372 String line; 373 while ((line = reader.readLine()) != null) { 374 stringBuilder.append(line); 375 } 376 fileInputStream.close(); 377 parcelFileDescriptor.close(); 378 return stringBuilder.toString(); 379 } 380 </pre> 381 382 <h3 id="create">Create a new document</h3> 383 384 <p>Your app can create a new document in a document provider using the 385 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 386 intent. To create a file you give your intent a MIME type and a file name, and 387 launch it with a unique request code. The rest is taken care of for you:</p> 388 389 390 <pre> 391 // Here are some examples of how you might call this method. 392 // The first parameter is the MIME type, and the second parameter is the name 393 // of the file you are creating: 394 // 395 // createFile("text/plain", "foobar.txt"); 396 // createFile("image/png", "mypicture.png"); 397 398 // Unique request code. 399 private static final int WRITE_REQUEST_CODE = 43; 400 ... 401 private void createFile(String mimeType, String fileName) { 402 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); 403 404 // Filter to only show results that can be "opened", such as 405 // a file (as opposed to a list of contacts or timezones). 406 intent.addCategory(Intent.CATEGORY_OPENABLE); 407 408 // Create a file with the requested MIME type. 409 intent.setType(mimeType); 410 intent.putExtra(Intent.EXTRA_TITLE, fileName); 411 startActivityForResult(intent, WRITE_REQUEST_CODE); 412 } 413 </pre> 414 415 <p>Once you create a new document you can get its URI in 416 {@link android.app.Activity#onActivityResult onActivityResult()}, so that you 417 can continue to write to it.</p> 418 419 <h3 id="delete">Delete a document</h3> 420 421 <p>If you have the URI for a document and the document's 422 {@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} 423 contains 424 {@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, 425 you can delete the document. For example:</p> 426 427 <pre> 428 DocumentsContract.deleteDocument(getContentResolver(), uri); 429 </pre> 430 431 <h3 id="edit">Edit a document</h3> 432 433 <p>You can use the Storage Access Framework to edit a text document in place. 434 This snippet fires 435 the {@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent and uses the 436 category {@link android.content.Intent#CATEGORY_OPENABLE} to to display only 437 documents that can be opened. It further filters to show only text files:</p> 438 439 <pre> 440 private static final int EDIT_REQUEST_CODE = 44; 441 /** 442 * Open a file for writing and append some text to it. 443 */ 444 private void editDocument() { 445 // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's 446 // file browser. 447 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 448 449 // Filter to only show results that can be "opened", such as a 450 // file (as opposed to a list of contacts or timezones). 451 intent.addCategory(Intent.CATEGORY_OPENABLE); 452 453 // Filter to show only text files. 454 intent.setType("text/plain"); 455 456 startActivityForResult(intent, EDIT_REQUEST_CODE); 457 } 458 </pre> 459 460 <p>Next, from {@link android.app.Activity#onActivityResult onActivityResult()} 461 (see <a href="#results">Process results</a>) you can call code to perform the edit. 462 The following snippet gets a {@link java.io.FileOutputStream} 463 from the {@link android.content.ContentResolver}. By default it uses write mode. 464 It's best practice to ask for the least amount of access you need, so dont ask 465 for read/write if all you need is write:</p> 466 467 <pre>private void alterDocument(Uri uri) { 468 try { 469 ParcelFileDescriptor pfd = getActivity().getContentResolver(). 470 openFileDescriptor(uri, "w"); 471 FileOutputStream fileOutputStream = 472 new FileOutputStream(pfd.getFileDescriptor()); 473 fileOutputStream.write(("Overwritten by MyCloud at " + 474 System.currentTimeMillis() + "\n").getBytes()); 475 // Let the document provider know you're done by closing the stream. 476 fileOutputStream.close(); 477 pfd.close(); 478 } catch (FileNotFoundException e) { 479 e.printStackTrace(); 480 } catch (IOException e) { 481 e.printStackTrace(); 482 } 483 }</pre> 484 485 <h3 id="permissions">Persist permissions</h3> 486 487 <p>When your app opens a file for reading or writing, the system gives your 488 app a URI permission grant for that file. It lasts until the user's device restarts. 489 But suppose your app is an image-editing app, and you want users to be able to 490 access the last 5 images they edited, directly from your app. If the user's device has 491 restarted, you'd have to send the user back to the system picker to find the 492 files, which is obviously not ideal.</p> 493 494 <p>To prevent this from happening, you can persist the permissions the system 495 gives your app. Effectively, your app "takes" the persistable URI permission grant 496 that the system is offering. This gives the user continued access to the files 497 through your app, even if the device has been restarted:</p> 498 499 500 <pre>final int takeFlags = intent.getFlags() 501 & (Intent.FLAG_GRANT_READ_URI_PERMISSION 502 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); 503 // Check for the freshest data. 504 getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre> 505 506 <p>There is one final step. You may have saved the most 507 recent URIs your app accessed, but they may no longer be valid—another app 508 may have deleted or modified a document. Thus, you should always call 509 {@code getContentResolver().takePersistableUriPermission()} to check for the 510 freshest data.</p> 511 512 <h2 id="custom">Writing a Custom Document Provider</h2> 513 514 <p> 515 If you're developing an app that provides storage services for files (such as 516 a cloud save service), you can make your files available through the Storage 517 Access Framework by writing a custom document provider. This section describes 518 how to do this.</p> 519 520 521 <h3 id="manifest">Manifest</h3> 522 523 <p>To implement a custom document provider, add the following to your application's 524 manifest:</p> 525 <ul> 526 527 <li>A target of API level 19 or higher.</li> 528 529 <li>A <code><provider></code> element that declares your custom storage 530 provider. </li> 531 532 <li>The name of your provider, which is its class name, including package name. 533 For example: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li> 534 535 <li>The name of your authority, which is your package name (in this example, 536 <code>com.example.android.storageprovider</code>) plus the type of content provider 537 (<code>documents</code>). For example, {@code com.example.android.storageprovider.documents}.</li> 538 539 <li>The attribute <code>android:exported</code> set to <code>"true"</code>. 540 You must export your provider so that other apps can see it.</li> 541 542 <li>The attribute <code>android:grantUriPermissions</code> set to 543 <code>"true"</code>. This allows the system to grant other apps access 544 to content in your provider. For a discussion of how to persist a grant for 545 a particular document, see <a href="#permissions">Persist permissions</a>.</li> 546 547 <li>The {@code MANAGE_DOCUMENTS} permission. By default a provider is available 548 to everyone. Adding this permission restricts your provider to the system, 549 which is important for security. </li> 550 551 <li>An intent filter that includes the 552 {@code android.content.action.DOCUMENTS_PROVIDER} action, so that your provider 553 appears in the picker when the system searches for providers.</li> 554 555 </ul> 556 <p>Here are excerpts from a sample manifest that includes a provider:</p> 557 558 <pre><manifest... > 559 ... 560 <uses-sdk 561 android:minSdkVersion="19" 562 android:targetSdkVersion="19" /> 563 .... 564 <provider 565 android:name="com.example.android.storageprovider.MyCloudProvider" 566 android:authorities="com.example.android.storageprovider.documents" 567 android:grantUriPermissions="true" 568 android:exported="true" 569 android:permission="android.permission.MANAGE_DOCUMENTS"> 570 <intent-filter> 571 <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> 572 </intent-filter> 573 </provider> 574 </application> 575 576 </manifest></pre> 577 578 <h4>Supporting devices running Android 4.3 and lower</h4> 579 580 <p>The 581 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} intent is only available 582 on devices running Android 4.4 and higher. 583 If you want your application to support {@link android.content.Intent#ACTION_GET_CONTENT} 584 to accommodate devices that are running Android 4.3 and lower, you should 585 disable the {@link android.content.Intent#ACTION_GET_CONTENT} intent filter in 586 your manifest if a device is running Android 4.4 or higher. A 587 document provider and {@link android.content.Intent#ACTION_GET_CONTENT} should be considered 588 mutually exclusive. If you support both of them simultaneously, your app will 589 appear twice in the system picker UI, offering two different ways of accessing 590 your stored data. This would be confusing for users.</p> 591 592 <p>Here is the recommended way of disabling the 593 {@link android.content.Intent#ACTION_GET_CONTENT} intent filter for devices 594 running Android version 4.4 or higher:</p> 595 596 <ol> 597 <li>In your {@code bool.xml} resources file under {@code res/values/}, add 598 this line: <pre><bool name="atMostJellyBeanMR2">true</bool></pre></li> 599 600 <li>In your {@code bool.xml} resources file under {@code res/values-v19/}, add 601 this line: <pre><bool name="atMostJellyBeanMR2">false</bool></pre></li> 602 603 <li>Add an 604 <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">activity 605 alias</a> to disable the {@link android.content.Intent#ACTION_GET_CONTENT} intent 606 filter for versions 4.4 (API level 19) and higher. For example: 607 608 <pre> 609 <!-- This activity alias is added so that GET_CONTENT intent-filter 610 can be disabled for builds on API level 19 and higher. --> 611 <activity-alias android:name="com.android.example.app.MyPicker" 612 android:targetActivity="com.android.example.app.MyActivity" 613 ... 614 android:enabled="@bool/atMostJellyBeanMR2"> 615 <intent-filter> 616 <action android:name="android.intent.action.GET_CONTENT" /> 617 <category android:name="android.intent.category.OPENABLE" /> 618 <category android:name="android.intent.category.DEFAULT" /> 619 <data android:mimeType="image/*" /> 620 <data android:mimeType="video/*" /> 621 </intent-filter> 622 </activity-alias> 623 </pre> 624 </li> 625 </ol> 626 <h3 id="contract">Contracts</h3> 627 628 <p>Usually when you write a custom content provider, one of the tasks is 629 implementing contract classes, as described in the 630 <a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass"> 631 Content Providers</a> developers guide. A contract class is a {@code public final} class 632 that contains constant definitions for the URIs, column names, MIME types, and 633 other metadata that pertain to the provider. The Storage Access Framework 634 provides these contract classes for you, so you don't need to write your 635 own:</p> 636 637 <ul> 638 <li>{@link android.provider.DocumentsContract.Document}</li> 639 <li>{@link android.provider.DocumentsContract.Root}</li> 640 </ul> 641 642 <p>For example, here are the columns you might return in a cursor when 643 your document provider is queried for documents or the root:</p> 644 645 <pre>private static final String[] DEFAULT_ROOT_PROJECTION = 646 new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, 647 Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, 648 Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, 649 Root.COLUMN_AVAILABLE_BYTES,}; 650 private static final String[] DEFAULT_DOCUMENT_PROJECTION = new 651 String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, 652 Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, 653 Document.COLUMN_FLAGS, Document.COLUMN_SIZE,}; 654 </pre> 655 656 <h3 id="subclass">Subclass DocumentsProvider</h3> 657 658 <p>The next step in writing a custom document provider is to subclass the 659 abstract class {@link android.provider.DocumentsProvider}. At minimum, you need 660 to implement the following methods:</p> 661 662 <ul> 663 <li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li> 664 665 <li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li> 666 667 <li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li> 668 669 <li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li> 670 </ul> 671 672 <p>These are the only methods you are strictly required to implement, but there 673 are many more you might want to. See {@link android.provider.DocumentsProvider} 674 for details.</p> 675 676 <h4 id="queryRoots">Implement queryRoots</h4> 677 678 <p>Your implementation of {@link android.provider.DocumentsProvider#queryRoots 679 queryRoots()} must return a {@link android.database.Cursor} pointing to all the 680 root directories of your document providers, using columns defined in 681 {@link android.provider.DocumentsContract.Root}.</p> 682 683 <p>In the following snippet, the {@code projection} parameter represents the 684 specific fields the caller wants to get back. The snippet creates a new cursor 685 and adds one row to it—one root, a top level directory, like 686 Downloads or Images. Most providers only have one root. You might have more than one, 687 for example, in the case of multiple user accounts. In that case, just add a 688 second row to the cursor.</p> 689 690 <pre> 691 @Override 692 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 693 694 // Create a cursor with either the requested fields, or the default 695 // projection if "projection" is null. 696 final MatrixCursor result = 697 new MatrixCursor(resolveRootProjection(projection)); 698 699 // If user is not logged in, return an empty root cursor. This removes our 700 // provider from the list entirely. 701 if (!isUserLoggedIn()) { 702 return result; 703 } 704 705 // It's possible to have multiple roots (e.g. for multiple accounts in the 706 // same app) -- just add multiple cursor rows. 707 // Construct one row for a root called "MyCloud". 708 final MatrixCursor.RowBuilder row = result.newRow(); 709 row.add(Root.COLUMN_ROOT_ID, ROOT); 710 row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary)); 711 712 // FLAG_SUPPORTS_CREATE means at least one directory under the root supports 713 // creating documents. FLAG_SUPPORTS_RECENTS means your application's most 714 // recently used documents will show up in the "Recents" category. 715 // FLAG_SUPPORTS_SEARCH allows users to search all documents the application 716 // shares. 717 row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | 718 Root.FLAG_SUPPORTS_RECENTS | 719 Root.FLAG_SUPPORTS_SEARCH); 720 721 // COLUMN_TITLE is the root title (e.g. Gallery, Drive). 722 row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title)); 723 724 // This document id cannot change once it's shared. 725 row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir)); 726 727 // The child MIME types are used to filter the roots and only present to the 728 // user roots that contain the desired type somewhere in their file hierarchy. 729 row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir)); 730 row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace()); 731 row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); 732 733 return result; 734 }</pre> 735 736 <h4 id="queryChildDocuments">Implement queryChildDocuments</h4> 737 738 <p>Your implementation of 739 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 740 must return a {@link android.database.Cursor} that points to all the files in 741 the specified directory, using columns defined in 742 {@link android.provider.DocumentsContract.Document}.</p> 743 744 <p>This method gets called when you choose an application root in the picker UI. 745 It gets the child documents of a directory under the root. It can be called at any level in 746 the file hierarchy, not just the root. This snippet 747 makes a new cursor with the requested columns, then adds information about 748 every immediate child in the parent directory to the cursor. 749 A child can be an image, another directory—any file:</p> 750 751 <pre>@Override 752 public Cursor queryChildDocuments(String parentDocumentId, String[] projection, 753 String sortOrder) throws FileNotFoundException { 754 755 final MatrixCursor result = new 756 MatrixCursor(resolveDocumentProjection(projection)); 757 final File parent = getFileForDocId(parentDocumentId); 758 for (File file : parent.listFiles()) { 759 // Adds the file's display name, MIME type, size, and so on. 760 includeFile(result, null, file); 761 } 762 return result; 763 } 764 </pre> 765 766 <h4 id="queryDocument">Implement queryDocument</h4> 767 768 <p>Your implementation of 769 {@link android.provider.DocumentsProvider#queryDocument queryDocument()} 770 must return a {@link android.database.Cursor} that points to the specified file, 771 using columns defined in {@link android.provider.DocumentsContract.Document}. 772 </p> 773 774 <p>The {@link android.provider.DocumentsProvider#queryDocument queryDocument()} 775 method returns the same information that was passed in 776 {@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, 777 but for a specific file:</p> 778 779 780 <pre>@Override 781 public Cursor queryDocument(String documentId, String[] projection) throws 782 FileNotFoundException { 783 784 // Create a cursor with the requested projection, or the default projection. 785 final MatrixCursor result = new 786 MatrixCursor(resolveDocumentProjection(projection)); 787 includeFile(result, documentId, null); 788 return result; 789 } 790 </pre> 791 792 <h4 id="openDocument">Implement openDocument</h4> 793 794 <p>You must implement {@link android.provider.DocumentsProvider#openDocument 795 openDocument()} to return a {@link android.os.ParcelFileDescriptor} representing 796 the specified file. Other apps can use the returned {@link android.os.ParcelFileDescriptor} 797 to stream data. The system calls this method once the user selects a file 798 and the client app requests access to it by calling 799 {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. 800 For example:</p> 801 802 <pre>@Override 803 public ParcelFileDescriptor openDocument(final String documentId, 804 final String mode, 805 CancellationSignal signal) throws 806 FileNotFoundException { 807 Log.v(TAG, "openDocument, mode: " + mode); 808 // It's OK to do network operations in this method to download the document, 809 // as long as you periodically check the CancellationSignal. If you have an 810 // extremely large file to transfer from the network, a better solution may 811 // be pipes or sockets (see ParcelFileDescriptor for helper methods). 812 813 final File file = getFileForDocId(documentId); 814 815 final boolean isWrite = (mode.indexOf('w') != -1); 816 if(isWrite) { 817 // Attach a close listener if the document is opened in write mode. 818 try { 819 Handler handler = new Handler(getContext().getMainLooper()); 820 return ParcelFileDescriptor.open(file, accessMode, handler, 821 new ParcelFileDescriptor.OnCloseListener() { 822 @Override 823 public void onClose(IOException e) { 824 825 // Update the file with the cloud server. The client is done 826 // writing. 827 Log.i(TAG, "A file with id " + 828 documentId + " has been closed! 829 Time to " + 830 "update the server."); 831 } 832 833 }); 834 } catch (IOException e) { 835 throw new FileNotFoundException("Failed to open document with id " 836 + documentId + " and mode " + mode); 837 } 838 } else { 839 return ParcelFileDescriptor.open(file, accessMode); 840 } 841 } 842 </pre> 843 844 <h3 id="security">Security</h3> 845 846 <p>Suppose your document provider is a password-protected cloud storage service 847 and you want to make sure that users are logged in before you start sharing their files. 848 What should your app do if the user is not logged in? The solution is to return 849 zero roots in your implementation of {@link android.provider.DocumentsProvider#queryRoots 850 queryRoots()}. That is, an empty root cursor:</p> 851 852 <pre> 853 public Cursor queryRoots(String[] projection) throws FileNotFoundException { 854 ... 855 // If user is not logged in, return an empty root cursor. This removes our 856 // provider from the list entirely. 857 if (!isUserLoggedIn()) { 858 return result; 859 } 860 </pre> 861 862 <p>The other step is to call {@code getContentResolver().notifyChange()}. 863 Remember the {@link android.provider.DocumentsContract}? Were using it to make 864 this URI. The following snippet tells the system to query the roots of your 865 document provider whenever the user's login status changes. If the user is not 866 logged in, a call to {@link android.provider.DocumentsProvider#queryRoots queryRoots()} returns an 867 empty cursor, as shown above. This ensures that a provider's documents are only 868 available if the user is logged into the provider.</p> 869 870 <pre>private void onLoginButtonClick() { 871 loginOrLogout(); 872 getContentResolver().notifyChange(DocumentsContract 873 .buildRootsUri(AUTHORITY), null); 874 } 875 </pre> 876