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