• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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>&mdash;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>&mdash;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>&mdash;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&quot;roots&quot; 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&mdash;for example, &quot;give me all openable files
183that have the 'image' MIME type.&quot;</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 &quot;file chooser&quot; 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 &quot;opened&quot;, 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 &quot;audio/ogg&quot;.
270    // To search for all documents available via installed storage providers,
271    // it would be &quot;*/*&quot;.
272    intent.setType(&quot;image/*&quot;);
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(&quot;image/*&quot;)</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>&#64;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    // &quot;if there's anything to look at, look at it&quot; conditionals.
337        if (cursor != null &amp;&amp; cursor.moveToFirst()) {
338
339            // Note it's called &quot;Display Name&quot;.  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, &quot;Display Name: &quot; + 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 &quot;unpredictable&quot;.  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 = &quot;Unknown&quot;;
359            }
360            Log.i(TAG, &quot;Size: &quot; + 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 &quot;opened&quot;, 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 &quot;opened&quot;, 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(&quot;text/plain&quot;);
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            &amp; (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&mdash;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>&lt;provider&gt;</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>&quot;true&quot;</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>&quot;true&quot;</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=&quot;&#64;bool/atLeastKitKat&quot;</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>&lt;bool name=&quot;atLeastKitKat&quot;&gt;false&lt;/bool&gt;</pre></li>
588
589<li>In your {@code bool.xml} resources file under {@code res/values-v19/}, add
590this line: <pre>&lt;bool name=&quot;atLeastKitKat&quot;&gt;true&lt;/bool&gt;</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>&lt;manifest... &gt;
601    ...
602    &lt;uses-sdk
603        android:minSdkVersion=&quot;19&quot;
604        android:targetSdkVersion=&quot;19&quot; /&gt;
605        ....
606        &lt;provider
607            android:name=&quot;com.example.android.storageprovider.MyCloudProvider&quot;
608            android:authorities=&quot;com.example.android.storageprovider.documents&quot;
609            android:grantUriPermissions=&quot;true&quot;
610            android:exported=&quot;true&quot;
611            android:permission=&quot;android.permission.MANAGE_DOCUMENTS&quot;
612            android:enabled=&quot;&#64;bool/atLeastKitKat&quot;&gt;
613            &lt;intent-filter&gt;
614                &lt;action android:name=&quot;android.content.action.DOCUMENTS_PROVIDER&quot; /&gt;
615            &lt;/intent-filter&gt;
616        &lt;/provider&gt;
617    &lt;/application&gt;
618
619&lt;/manifest&gt;</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>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;true&lt;/bool&gt;</pre></li>
642
643<li>In your {@code bool.xml} resources file under {@code res/values-v19/}, add
644this line: <pre>&lt;bool name=&quot;atMostJellyBeanMR2&quot;&gt;false&lt;/bool&gt;</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&lt;!-- This activity alias is added so that GET_CONTENT intent-filter
653     can be disabled for builds on API level 19 and higher. --&gt;
654&lt;activity-alias android:name=&quot;com.android.example.app.MyPicker&quot;
655        android:targetActivity=&quot;com.android.example.app.MyActivity&quot;
656        ...
657        android:enabled=&quot;@bool/atMostJellyBeanMR2&quot;&gt;
658    &lt;intent-filter&gt;
659        &lt;action android:name=&quot;android.intent.action.GET_CONTENT&quot; /&gt;
660        &lt;category android:name=&quot;android.intent.category.OPENABLE&quot; /&gt;
661        &lt;category android:name=&quot;android.intent.category.DEFAULT&quot; /&gt;
662        &lt;data android:mimeType=&quot;image/*&quot; /&gt;
663        &lt;data android:mimeType=&quot;video/*&quot; /&gt;
664    &lt;/intent-filter&gt;
665&lt;/activity-alias&gt;
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&mdash;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&#64;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 &quot;MyCloud&quot;.
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 &quot;Recents&quot; 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&mdash;any file:</p>
793
794<pre>&#64;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>&#64;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>&#64;Override
846public ParcelFileDescriptor openDocument(final String documentId,
847                                         final String mode,
848                                         CancellationSignal signal) throws
849        FileNotFoundException {
850    Log.v(TAG, &quot;openDocument, mode: &quot; + 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                &#64;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, &quot;A file with id &quot; +
871                    documentId + &quot; has been closed!
872                    Time to &quot; +
873                    &quot;update the server.&quot;);
874                }
875
876            });
877        } catch (IOException e) {
878            throw new FileNotFoundException(&quot;Failed to open document with id &quot;
879            + documentId + &quot; and mode &quot; + 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>