Google is committed to advancing racial equity for Black communities. See how.

USB Storage Media

MediaStore

MediaStore is the recommended way for apps to browse and access media content on Android. MediaStore indexes all adoptable volumes and then makes their audio, video, and image content available to applications. LocalMediaPlayer is an example usage of the MediaStore API.

Adoptable Storage

A drive can be considered adoptable if it is connected in a stable location where it is unlikely to get disconnected frequently. USB ports can be indicated as stable in the fstab by using the encryptable=userdata attribute (see Device Configuration).

Any drive connected to an adoptable port will be indexed. In addition to taking processing time, the resulting index is written to the internal flash storage.

Known limitations in Android 9

In Android 9, the index for an adoptable drive was deleted when a drive was unplugged, which means that if a user removed and immediately re-attached a drive, the original index would be deleted from flash storage and then rewritten when the drive is reindexed.

Android 9 also contained a race condition on startup in which a drive could be mistaken as disconnected and then go through this same process of deleting and reindexing. Worst case, a rewrite to local flash storage could occur each time a car is turned on.

What's new in Android 10

Android 10 provides a number of MediaStore improvements. For example, a fix to the race condition described above. When a drive is removed, its index will be retained for one week since the date on which the drive was last detected). If the drive is reconnected during this window, the index is not rewritten to flash storage. The framework will do this for multiple drives.

When a drive is reconnected, if the contents have changed, then the index is updated instead of entirely rewritten. This minimizes the impact to flash storage. This check also occurs on start-up. If a user removes the drive after turning the vehicle off, adds some new media, and then reconnects the drive before starting the vehicle, the drive's index will be updated to include the newly added media.

Other requirements

By setting a port as adoptable, any drive connected to it can be adopted to be part of system storage. Once this occurs, the drive is treated as an extension of the device's storage and should not be removed without first converting it back to being a portable media.

When the drive is connected, the framework triggers a notification that allows users to adopt the drive. This starts a wizard flow that enables users to format the drive. In Android 10, this flow is not built into Car settings. It is the responsibility of the OEM to make sure this functionality is supported when marking ports as adoptable. CtsAppSecurityHostTestCases covers functionality related to adoptable ports and must be run.

Detecting changes in media

A running process (activity or service) can register a ContentObserver on MediaStore.AUTHORITY_URI, which will enable the observer to be notified of content changes across all adoptable volumes.

ContentObserver mContentObserver = new ContentObserver(new Handler()) {
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        super.onChange(selfChange, uri);
        if (isResumed()) {
            // take action here
        }
    }
};

context.getContentResolver().registerContentObserver(
MediaStore.AUTHORITY_URI, true, mContentObserver);

By setting notifyForDescendants to true, the observer is notified of any changes in available media under that URI, instead of explicity for that URI. As a result, while a newly connected drive is being indexed, this observer is triggered with each newly added URI.

In Android 10, you can use JobScheduler to monitor for changes in content without a running process. However, the job is executed only once the device has the available resources.

Listing volumes

In Android 10, the names of all adoptable volumes that have or are being indexed can be retrieved using a new Media Store API:

List<String> volumeNames = MediaStore.getExternalVolumeNames(context);

Browsing volume content

With the name of an adoptable volume, processes can get a URI for specific types of media within that volume (Audio, Video, Images, or Files) and query for all of the available indexed files under it:

import android.provider.MediaStore.Audio.Media;

Uri volumeAudioUri = Media.getContentUri(volumeName);
String[] projection = {Media._ID, Media.ARTIST, Media.TITLE};

Cursor cursor = getContext().getContentResolver().query(volumeAudioUri, projection, null, null);

Browsing across all volumes

Each content type under MediaStore has an EXTERNAL_CONTENT_URI and an INTERNAL_CONTENT_URI that can be used to get a list of all content across external and internal volumes. See packages/apps/Car/LocalMediaPlayer for an example of how to combine these two to see all available media.

Required permission

Any app seeking to read from external storage must request the READ_EXTERNAL_STORAGE permission.

Playing media

A URI can be generated for a specific cursor item:

Long mediaId = cursor.getLong(cursor.getColumnIndex(Media._ID));
Uri mediaUri = ContentUris.withAppendedId(volumeAudioUri, mediaId);

From there, the application can request audio focus and play the content. See Audio & Video Overview for more information on best practices for building media apps.

Alternatives to MediaStore

Using MediaStore and ContentResolver to access content on adoptable drives is the recommended approach. However, for non-adoptable USB ports, there are alternative ways to detect changes and access the content.

Detecting changes in connected volumes

System applications can listen for changes in connected volumes. A StorageEventListener can be notified whenever a volume's state has changed, including when a volume is being checked or removed as well as when it becomes mounted or unmounted.

StorageEventListener mStorageEventListener = new StorageEventListener() {
    @Override
    public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
        if (isResumed()) {
            // take action here
        }
    }
};

StorageManager mStorageManager = context.getSystemService(StorageManager.class);
mStorageManager.registerListener(mStorageEventListener);

This approach can only be used within a running process.

Access through Storage Access Framework

To access non-media files on an adoptable drive, or any files on non-adoptable drives, applications must use the Storage Access Framework (SAF). By triggering an ACTIONS_OPEN_DOCUMENT intent, the user will be presented with a system UI for picking one or more files from all volumes. The calling application for this intent must specify MIME types to filter the files that users are able to select down to those that the application are looking to take action on.

Note: An application cannot access files using the SAF without the user being prompted and explicitly selecting those files. Android provides a generic file UI access APK, which can either be extended to stylize it, or replaced with an OEM provided apk.