Home | History | Annotate | Download | only in providers
      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>&mdash;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>&mdash;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>&mdash;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 &quot;roots&quot; 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&mdash;for example, &quot;give me all openable files
    153 that have the 'image' MIME type.&quot;</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 &quot;file chooser&quot; 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 &quot;opened&quot;, 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 &quot;audio/ogg&quot;.
    240     // To search for all documents available via installed storage providers,
    241     // it would be &quot;*/*&quot;.
    242     intent.setType(&quot;image/*&quot;);
    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(&quot;image/*&quot;)} 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>&#64;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     // &quot;if there's anything to look at, look at it&quot; conditionals.
    307         if (cursor != null &amp;&amp; cursor.moveToFirst()) {
    308 
    309             // Note it's called &quot;Display Name&quot;.  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, &quot;Display Name: &quot; + 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 &quot;unpredictable&quot;.  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 = &quot;Unknown&quot;;
    329             }
    330             Log.i(TAG, &quot;Size: &quot; + 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 &quot;opened&quot;, 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 &quot;opened&quot;, 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(&quot;text/plain&quot;);
    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             &amp; (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&mdash;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>&lt;provider&gt;</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>&quot;true&quot;</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>&quot;true&quot;</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>&lt;manifest... &gt;
    559     ...
    560     &lt;uses-sdk
    561         android:minSdkVersion=&quot;19&quot;
    562         android:targetSdkVersion=&quot;19&quot; /&gt;
    563         ....
    564         &lt;provider
    565             android:name=&quot;com.example.android.storageprovider.MyCloudProvider&quot;
    566             android:authorities=&quot;com.example.android.storageprovider.documents&quot;
    567             android:grantUriPermissions=&quot;true&quot;
    568             android:exported=&quot;true&quot;
    569             android:permission=&quot;android.permission.MANAGE_DOCUMENTS&quot;&gt;
    570             &lt;intent-filter&gt;
    571                 &lt;action android:name=&quot;android.content.action.DOCUMENTS_PROVIDER&quot; /&gt;
    572             &lt;/intent-filter&gt;
    573         &lt;/provider&gt;
    574     &lt;/application&gt;
    575 
    576 &lt;/manifest&gt;</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>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;true&lt;/bool&gt;</pre></li>
    599 
    600 <li>In your {@code bool.xml} resources file under {@code res/values-v19/}, add
    601 this line: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;false&lt;/bool&gt;</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 &lt;!-- This activity alias is added so that GET_CONTENT intent-filter
    610      can be disabled for builds on API level 19 and higher. --&gt;
    611 &lt;activity-alias android:name=&quot;com.android.example.app.MyPicker&quot;
    612         android:targetActivity=&quot;com.android.example.app.MyActivity&quot;
    613         ...
    614         android:enabled=&quot;@bool/atMostJellyBeanMR2&quot;&gt;
    615     &lt;intent-filter&gt;
    616         &lt;action android:name=&quot;android.intent.action.GET_CONTENT&quot; /&gt;
    617         &lt;category android:name=&quot;android.intent.category.OPENABLE&quot; /&gt;
    618         &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;
    619         &lt;data android:mimeType=&quot;image/*&quot; /&gt;
    620         &lt;data android:mimeType=&quot;video/*&quot; /&gt;
    621     &lt;/intent-filter&gt;
    622 &lt;/activity-alias&gt;
    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&mdash;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 &#64;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 &quot;MyCloud&quot;.
    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 &quot;Recents&quot; 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&mdash;any file:</p>
    750 
    751 <pre>&#64;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>&#64;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>&#64;Override
    803 public ParcelFileDescriptor openDocument(final String documentId,
    804                                          final String mode,
    805                                          CancellationSignal signal) throws
    806         FileNotFoundException {
    807     Log.v(TAG, &quot;openDocument, mode: &quot; + 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                 &#64;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, &quot;A file with id &quot; +
    828                     documentId + &quot; has been closed!
    829                     Time to &quot; +
    830                     &quot;update the server.&quot;);
    831                 }
    832 
    833             });
    834         } catch (IOException e) {
    835             throw new FileNotFoundException(&quot;Failed to open document with id &quot;
    836             + documentId + &quot; and mode &quot; + 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