page.title=Using Scoped Directory Access page.keywords=scoped directory access @jd:body
Apps such as photo apps usually just need access to specific directories in
external storage, such as the Pictures
directory. Existing
approaches to accessing external storage aren't designed to easily provide
targeted directory access for these types of apps. For example:
Android 7.0 provides a simplified API to access common external storage directories.
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()}.
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.
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.
The following code snippet is an example of how to open the
Pictures
directory in the primary shared storage:
StorageManager sm = (StorageManager)getSystemService(Context.STORAGE_SERVICE); StorageVolume volume = sm.getPrimaryStorageVolume(); Intent intent = volume.createAccessIntent(Environment.DIRECTORY_PICTURES); startActivityForResult(intent, request_code);
The system attempts to grant access to the external directory, and if necessary confirms access with the user using a simplified UI:
Figure 1. An application requesting access to the Pictures directory.
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 Storage Access Framework.
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.
Getting access to a specific external directory also gains access to subdirectories within that directory.
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:
<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>
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 Pictures
directory on removable media:
// 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);
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.
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 Don't ask again checkbox:
Figure 1. An application making a second request for access to removable media.
If the user selects Don't ask again 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.