page.title=Using Scoped Directory Access page.keywords=scoped directory access @jd:body <div id="tb-wrapper"> <div id="tb"> <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 7.0 provides a simplified API to access common external storage directories.</p> <h2 id="accessing">Accessing an External Storage Directory</h2> <p>Use the {@link android.os.storage.StorageManager} class to get the appropriate {@link android.os.storage.StorageVolume} instance. Then, create an intent by calling the {@link android.os.storage.StorageVolume#createAccessIntent StorageVolume.createAccessIntent()} 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 {@link android.os.storage.StorageManager#getStorageVolumes StorageManager.getStorageVolumes()}.</p> <p>If you have information about a specific file, use {@link android.os.storage.StorageManager#getStorageVolume StorageManager.getStorageVolume(File)} to get the {@link android.os.storage.StorageVolume} that contains the file. Call {@link android.os.storage.StorageVolume#createAccessIntent createAccessIntent()} on this {@link android.os.storage.StorageVolume} to access the external storage directory for the file.</p> <p> On secondary volumes, such as external SD cards, pass in null when calling {@link android.os.storage.StorageVolume#createAccessIntent createAccessIntent()} to request access to the entire volume, instead of a specific directory. {@link android.os.storage.StorageVolume#createAccessIntent createAccessIntent()} 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.getPrimaryStorageVolume(); 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}images/android-7.0/scoped-directory-access-framed.png" srcset="{@docRoot}images/android-7.0/scoped-directory-access-framed.png 1x, {@docRoot}images/android-7.0/scoped-directory-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 {@link android.app.Activity#onActivityResult onActivityResult()} override with a result code of {@link android.app.Activity#RESULT_OK RESULT_OK}, 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 {@link android.app.Activity#onActivityResult onActivityResult()} override with a result code of {@link android.app.Activity#RESULT_CANCELED RESULT_CANCELED}, and null intent data.</p> <p>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 {@link android.os.storage.StorageVolume} 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 {@link android.content.Context#getContentResolver getContentResolver()} and with the returned {@link android.content.ContentResolver} call {@link android.content.ContentResolver#takePersistableUriPermission takePersistableUriPermission()} with the directory access URI. The system will persist the URI and subsequent access requests will return {@link android.app.Activity#RESULT_OK RESULT_OK} 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}images/android-7.0/scoped-directory-access-dont-ask.png" srcset="{@docRoot}images/android-7.0/scoped-directory-access-dont-ask.png 1x, {@docRoot}images/android-7.0/scoped-directory-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>