| page.title=Scoped Directory Access |
| page.keywords=preview,sdk,scoped directory access |
| page.tags=androidn |
| |
| @jd:body |
| |
| <div id="qv-wrapper"> |
| <div id="qv"> |
| <h2>In this document</h2> |
| <ol> |
| <li><a href="#accessing">Accessing an External Storage Directory</a></li> |
| <li><a href="#removable">Accessing a Directory on Removable Media</a></li> |
| <li><a href="#best">Best Practices</a></li> |
| </ol> |
| </div> |
| </div> |
| |
| <p>Apps such as photo apps usually just need access to specific directories in |
| external storage, such as the <code>Pictures</code> directory. Existing |
| approaches to accessing external storage aren't designed to easily provide |
| targeted directory access for these types of apps. For example:</p> |
| |
| <ul> |
| <li>Requesting {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} |
| or {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} in your manifest |
| allows access to all public directories on external storage, which might be |
| more access than what your app needs.</li> |
| <li>Using the |
| <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage |
| Access Framework</a> usually makes your user pick directories |
| via a system UI, which is unnecessary if your app always accesses the same |
| external directory.</li> |
| </ul> |
| |
| <p>Android N provides a new simplified API to access |
| common external storage directories. </p> |
| |
| <h2 id="accessing">Accessing an External Storage Directory</h2> |
| |
| <p>Use the <code>StorageManager</code> class to get the appropriate |
| <code>StorageVolume</code> instance. Then, create an intent by calling the |
| <code>StorageVolume.createAccessIntent()</code> method of that instance. |
| Use this intent to access external storage directories. To get a list of |
| all available volumes, including removable media volumes, use |
| <code>StorageManager.getVolumesList()</code>.</p> |
| |
| <p>If you have information about a specific file, use |
| <code>StorageManager.getStorageVolume(File)</code> to get the |
| <code>StorageVolume</code> that contains the file. Call |
| <code>createAccessIntent()</code> on this <code>StorageVolume</code> to access |
| the external storage directory for the file.</p> |
| |
| <p> |
| On secondary volumes, such as external SD cards, pass in null when calling |
| <code>StorageVolume.createAccessIntent()</code> to request access to the entire |
| volume, instead of a specific directory. |
| <code>StorageVolume.createAccessIntent()</code> returns null if you pass in |
| null to the primary volume, or if you pass in an invalid directory name. |
| </p> |
| |
| <p>The following code snippet is an example of how to open the |
| <code>Pictures</code> directory in the primary shared storage:</p> |
| |
| <pre> |
| StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE); |
| StorageVolume volume = sm.getPrimaryVolume(); |
| Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES); |
| startActivityForResult(intent, request_code); |
| </pre> |
| |
| <p>The system attempts to grant access to the external directory, and if |
| necessary confirms access with the user using a simplified UI:</p> |
| |
| <img src="{@docRoot}preview/images/scoped-folder-access-framed.png" |
| srcset="{@docRoot}preview/images/scoped-folder-access-framed.png 1x, |
| {@docRoot}preview/images/scoped-folder-access-framed_2x.png 2x" /> |
| <p class="img-caption"><strong>Figure 1.</strong> An application requesting |
| access to the Pictures directory.</p> |
| |
| <p>If the user grants access, the system calls your |
| <code>onActivityResult()</code> override with a result code of |
| <code>Activity.RESULT_OK</code>, and intent data that contains the URI. Use |
| the provided URI to access directory information, similar to using URIs |
| returned by the |
| <a href="{@docRoot}guide/topics/providers/document-provider.html">Storage |
| Access Framework</a>.</p> |
| |
| <p>If the user doesn't grant access, the system calls your |
| <code>onActivityResult()</code> override with a result code of |
| <code>Activity.RESULT_CANCELED</code>, and null intent data.</p> |
| |
| <p class="note"><b>Note</b>: Getting access to a specific external directory |
| also gains access to subdirectories within that directory.</p> |
| |
| <h2 id="removable">Accessing a Directory on Removable Media</h2> |
| |
| <p>To use Scoped Directory Access to access directories on removable media, |
| first add a {@link android.content.BroadcastReceiver} that listens for the |
| {@link android.os.Environment#MEDIA_MOUNTED} notification, for example:</p> |
| |
| <pre> |
| <receiver |
| android:name=".MediaMountedReceiver" |
| android:enabled="true" |
| android:exported="true" > |
| <intent-filter> |
| <action android:name="android.intent.action.MEDIA_MOUNTED" /> |
| <data android:scheme="file" /> |
| </intent-filter> |
| </receiver> |
| </pre> |
| |
| <p>When the user mounts removable media, like an SD card, the system sends a |
| {@link android.os.Environment#MEDIA_MOUNTED} notification. This notification |
| provides a <code>StorageVolume</code> object in the intent data that you can |
| use to access directories on the removable media. The following example |
| accesses the <code>Pictures</code> directory on removable media:</p> |
| |
| <pre> |
| // BroadcastReceiver has already cached the MEDIA_MOUNTED |
| // notification Intent in mediaMountedIntent |
| StorageVolume volume = (StorageVolume) |
| mediaMountedIntent.getParcelableExtra(StorageVolume.EXTRA_STORAGE_VOLUME); |
| volume.createAccessIntent(Environment.DIRECTORY_PICTURES); |
| startActivityForResult(intent, request_code); |
| </pre> |
| |
| <h2 id="best">Best Practices</h2> |
| |
| <p>Where possible, persist the external directory access URI so you don't have |
| to repeatedly ask the user for access. Once the user has granted access, call |
| <code>getContentResolver().takePersistableUriPermssion()</code> with the |
| directory access URI. The system will persist the URI and subsequent access |
| requests will return <code>RESULT_OK</code> and not show confirmation UI to the |
| user.</p> |
| |
| <p>If the user denies access to an external directory, do not immediately |
| request access again. Repeatedly insisting on access results in a poor user |
| experience. If a request is denied by the user, and the app requests access |
| again, the UI displays a <b>Don't ask again</b> checkbox:</p> |
| |
| <img src="{@docRoot}preview/images/scoped-folder-access-dont-ask.png" |
| srcset="{@docRoot}preview/images/scoped-folder-access-dont-ask.png 1x, |
| {@docRoot}preview/images/scoped-folder-access-dont-ask_2x.png 2x" /> |
| <p class="img-caption"><strong>Figure 1.</strong> An application making a |
| second request for access to removable media.</p> |
| |
| <p>If the user selects <b>Don't ask again</b> and denies the request, all |
| future requests for the given directory from your app will be automatically |
| denied, and no request UI will be presented to the user.</p> |