Most visited

Recently visited

Using Scoped Directory Access

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:

  • Requesting READ_EXTERNAL_STORAGE or 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.
  • Using the Storage Access Framework usually makes your user pick directories via a system UI, which is unnecessary if your app always accesses the same external directory.

Android 7.0 provides a simplified API to access common external storage directories.

Accessing an External Storage Directory

Use the StorageManager class to get the appropriate StorageVolume instance. Then, create an intent by calling the 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 StorageManager.getStorageVolumes().

If you have information about a specific file, use StorageManager.getStorageVolume(File) to get the StorageVolume that contains the file. Call createAccessIntent() on this StorageVolume to access the external storage directory for the file.

On secondary volumes, such as external SD cards, pass in null when calling createAccessIntent() to request access to the entire volume, instead of a specific directory. 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 onActivityResult() override with a result code of 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 onActivityResult() override with a result code of RESULT_CANCELED, and null intent data.

Getting access to a specific external directory also gains access to subdirectories within that directory.

Accessing a Directory on Removable Media

To use Scoped Directory Access to access directories on removable media, first add a BroadcastReceiver that listens for the 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 MEDIA_MOUNTED notification. This notification provides a 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);

Best Practices

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 getContentResolver() and with the returned ContentResolver call takePersistableUriPermission() with the directory access URI. The system will persist the URI and subsequent access requests will return 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.

Hooray!