The Android Developer Challenge is back! Submit your idea before December 2.

Work with media files in external storage

The MediaStore APIs provide an interface for accessing the following well-defined types of media files:

The MediaStore also includes a collection called MediaStore.Files, which provides access to all types of media files.

This guide describes how to access and share media files, typically stored on an external storage device.

Access files

To load media files, call one of the following methods from ContentResolver:

  • For a single media file, use openFileDescriptor().
  • For the thumbnail of a single media file, use loadThumbnail(), passing in the size of the thumbnail that you want to load.
  • For a collection of media files, use query().

The following code snippet shows how to access media files. A complete example of how to query the MediaStore.Images collection is available on GitHub.

val resolver = context.getContentResolver()

// Open a specific media item.
resolver.openFileDescriptor(item, mode).use { pfd ->
    // ...
}

// Load thumbnail of a specific media item.
val mediaThumbnail = resolver.loadThumbnail(item, Size(640, 480), null)

// Find all videos on a given storage device, including pending files.
val collection = MediaStore.Video.Media.getContentUri(volumeName)
val collectionWithPending = MediaStore.setIncludePending(collection)
resolver.query(collectionWithPending, null, null, null).use { c ->
    // ...
}

// Publish a video onto an external storage device.
val values = ContentValues().apply {
    put(MediaStore.Audio.Media.RELATIVE_PATH, "Video/My Videos")
    put(MediaStore.Audio.Media.DISPLAY_NAME, "My Video.mp4")
}
val item = resolver.insert(collection, values)

Access from native code

You might encounter situations where your app needs to work with a particular media file in native code, such as a file that another app has shared with your app or a media file from the user's media collection. In these cases, begin your media file discovery in your Java-based or Koltin-based code, then pass the file's associated file descriptor into your native code.

The following code snippet shows how to pass a media object's file descriptor into your app's native code:

Kotlin

val contentUri: Uri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(BaseColumns._ID))
val fileOpenMode = "r"
val parcelFd = resolver.openFileDescriptor(uri, fileOpenMode)
val fd = parcelFd?.detachFd()
// Pass the integer value "fd" into your native code. Remember to call
// close(2) on the file descriptor when you're done using it.

Java

Uri contentUri = ContentUris.withAppendedId(
        MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
        cursor.getLong(Integer.parseInt(BaseColumns._ID)));
String fileOpenMode = "r";
ParcelFileDescriptor parcelFd = resolver.openFileDescriptor(uri, fileOpenMode);
if (parcelFd != null) {
    int fd = parcelFd.detachFd();
    // Pass the integer value "fd" into your native code. Remember to call
    // close(2) on the file descriptor when you're done using it.
}

To learn more about accessing files in native code, see the Files for Miles talk from Android Dev Summit '18, starting at 15:20.

Column names in content queries

If your app's code uses a column name projection, such as mime_type AS MimeType, keep in mind that devices running Android 10 (API level 29) and higher require column names that are defined in the MediaStore API.

If a dependent library within your app expects a column name that's undefined in the Android API, such as MimeType, use CursorWrapper to dynamically translate the column name in your app's process.

Provide pending status for media files being stored

On devices running Android 10 (API level 29) and higher, your app can get exclusive access to a media file as it's written to disk by using the IS_PENDING flag.

The following code snippet shows how to use the IS_PENDING flag when storing a picture in the directory corresponding to the MediaStore.Images collection:

Kotlin

val values = ContentValues().apply {
    put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG")
    put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg")
    put(MediaStore.Images.Media.IS_PENDING, 1)
}

val resolver = context.getContentResolver()
val collection = MediaStore.Images.Media
        .getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val item = resolver.insert(collection, values)

resolver.openFileDescriptor(item, "w", null).use { pfd ->
    // Write data into the pending image.
}

// Now that we're finished, release the "pending" status, and allow other apps
// to view the image.
values.clear()
values.put(MediaStore.Images.Media.IS_PENDING, 0)
resolver.update(item, values, null, null)

Java

ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, "IMG1024.JPG");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
values.put(MediaStore.Images.Media.IS_PENDING, 1);

ContentResolver resolver = context.getContentResolver();
Uri collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY);
Uri item = resolver.insert(collection, values);

try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(item, "w", null)) {
    // Write data into the pending image.
} catch (IOException e) {
    e.printStackTrace();
}

// Now that we're finished, release the "pending" status, and allow other apps
// to view the image.
values.clear();
values.put(MediaStore.Images.Media.IS_PENDING, 0);
resolver.update(item, values, null, null);

Update other apps' media files

If your app uses scoped storage, it ordinarily cannot update a media file that a different app contributed to the media store. It's still possible to get user consent to modify the file, however, by catching the RecoverableSecurityException that the platform throws. You can then request that the user grant your app write access to that specific item, as shown in the following code snippet:

Kotlin

try {
    // ...
} catch (rse: RecoverableSecurityException) {
    val requestAccessIntentSender = rse.userAction.actionIntent.intentSender

    // In your code, handle IntentSender.SendIntentException.
    startIntentSenderForResult(requestAccessIntentSender, your-request-code,
            null, 0, 0, 0, null)
}

Java

try {
    // ...
} catch (RecoverableSecurityException rse) {
    IntentSender requestAccessIntentSender = rse.getUserAction()
            .getActionIntent().getIntentSender();

    // In your code, handle IntentSender.SendIntentException.
    startIntentSenderForResult(requestAccessIntentSender, your-request-code,
            null, 0, 0, 0, null);
}

Provide a hint for where files are located

When your app contributes media on a device running Android 10 (API level 29), the media is organized based on its type by default. For example, new image files are placed by default in the Environment.DIRECTORY_PICTURES directory, which corresponds to the MediaStore.Images collection.

If your app is aware of a specific location where files should be stored, such as Pictures/MyVacationPictures, you can set MediaColumns.RELATIVE_PATH to provide the system a hint for where to store the newly-written files. Similarly, you can move files on disk during a call to update() by changing MediaColumns.RELATIVE_PATH or MediaColumns.DISPLAY_NAME.

Common use cases

This section describes how to fulfill several common use cases related to media files.

Share media files

Some apps allow users to share media files with each other. For example, social media apps give users the ability to share photos and videos with friends.

To access the media files that users want to share, use the process described in the section about how to access files and how to access volumes using their unique names.

In cases where you provide a suite of companion apps—such as a messaging app and a profile app—set up file sharing using content:// URIs. We also recommend this workflow as a security best practice.

Work in documents

Some apps use documents as the unit of storage in which users enter data that they might want to share with peers or import into other documents. Several examples include a user opening a business productivity document or opening a book that's saved as an EPUB file.

In these cases, allow the user to choose the file to open by invoking the ACTION_OPEN_DOCUMENT intent, which opens the system's file picker app. To show only the types of files that your app supports, include the Intent.EXTRA_MIME_TYPES extra in your intent.

The ActionOpenDocument sample on GitHub shows how to use ACTION_OPEN_DOCUMENT to open a file after getting the user's consent.

Manage groups of files

File management and media-creation apps typically manage groups of files in a directory hierarchy. These apps can invoke the ACTION_OPEN_DOCUMENT_TREE intent to allow the user to grant access to an entire directory tree. Such an app would be able to edit any file in the selected directory and any of its sub-directories.

Using this interface, users can access files from an instance of DocumentsProvider, which any locally-backed or cloud-based solution can support.

The ActionOpenDocumentTree sample on GitHub shows how to use ACTION_OPEN_DOCUMENT_TREE to open a directory tree after getting the user's consent.