Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Manage scoped external storage access

To give users more control over their files and to limit file clutter, apps targeting Android 10 (API level 29) and higher are given scoped access into an external storage device, or scoped storage, by default. Such apps can see only their app-specific directory—accessed using Context.getExternalFilesDir()—and specific types of media. It's a best practice to use scoped storage unless your app needs access to a file that doesn't reside in either the app-specific directory or the MediaStore.

The following table summarizes how scoped storage affects file access:

File location Permissions needed Method of accessing (*) Files removed when app uninstalled?
App-specific directory None getExternalFilesDir() Yes
Media collections
(photos, videos, audio)
READ_EXTERNAL_STORAGE
only when
accessing other apps' files
MediaStore No
Downloads
(documents and
e-books)
None Storage Access Framework
(loads system's file picker)
No

*You can use the Storage Access Framework to access each of the locations shown in the preceding table without requesting any permissions.

This page describes the files that your app can access when using scoped storage, as well as how to update your app so that it can continue to share, access, and modify other files that are saved on an external storage device.

Permissions required for file access

An app that uses scoped storage always has read/write access to the files that it creates, both inside and outside its app-specific directory. As a result, if your app saves and accesses only the files that it creates, you don't need to request the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permission.

To access files that other apps have created, however, both of the following conditions must be true:

  1. Your app has been granted the READ_EXTERNAL_STORAGE permission.
  2. The files reside in one of the following well-defined media collections:

In order to access any other file that another app has created, including files in a "downloads" directory, your app must use the Storage Access Framework, which allows the user to select a specific file.

Even with the READ_EXTERNAL_STORAGE permission, if such an app accesses the raw file-system view of an external storage device, the app has access only to the app-specific directory. If an app attempts to open files outside of this directory using a raw file-system view, an error occurs:

Media data restrictions

Scoped storage imposes the following media-related data restrictions:

  • The Exif metadata within image files is redacted, unless your app has been granted the ACCESS_MEDIA_LOCATION permission. Learn more in the section about how to access location information in pictures.
  • The MediaStore.Files table is itself filtered, showing only photos, videos, and audio files. For example, the table doesn't show PDF files.
  • Any media file access must start in Java-based or Kotlin-based code using MediaStore in your Java-based or Kotlin-based code. See the guidance on how to access media files from native code.

The guide describing how to work with media files provides best practices for accessing individual documents and document trees within the MediaStore. If your app uses scoped storage, these methods for accessing media are required.

Location information in pictures

Some photographs contain location information in their Exif metadata, which allows users to view the place where a photograph was taken. Because this location information is sensitive, however, Android 10 by default hides this information from your app if it uses scoped storage.

If your app needs access to a photograph's location information, complete the following steps:

  1. Request the ACCESS_MEDIA_LOCATION permission in your app's manifest.
  2. From your MediaStore object, call setRequireOriginal(), passing in the URI of the photograph, as shown in the following code snippet:

    Kotlin

    // Get location data from the ExifInterface class.
    val photoUri = MediaStore.setRequireOriginal(photoUri)
    contentResolver.openInputStream(photoUri).use { stream ->
        ExifInterface(stream).run {
            // If lat/long is null, fall back to the coordinates (0, 0).
            val latLong = ?: doubleArrayOf(0.0, 0.0)
        }
    }
    

    Java

    Uri photoUri = Uri.withAppendedPath(
            MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            cursor.getString(idColumnIndex));
    
    final double[] latLong;
    
    // Get location data from the ExifInterface class.
    photoUri = MediaStore.setRequireOriginal(photoUri);
    InputStream stream = getContentResolver().openInputStream(photoUri);
    if (stream != null) {
        ExifInterface exifInterface = new ExifInterface(stream);
        double[] returnedLatLong = exifInterface.getLatLong();
    
        // If lat/long is null, fall back to the coordinates (0, 0).
        latLong = returnedLatLong != null ? returnedLatLong : new double[2];
    
        // Don't reuse the stream associated with the instance of "ExifInterface".
        stream.close();
    } else {
        // Failed to load the stream, so return the coordinates (0, 0).
        latLong = new double[2];
    }
    

Opt out of scoped storage

Before your app is fully compatible with scoped storage, you can temporarily opt out based on your app's target SDK level or the requestLegacyExternalStorage manifest attribute:

  • Target Android 9 (API level 28) or lower.
  • If you target Android 10 or higher, set the value of requestLegacyExternalStorage to true in your app's manifest file:

    <manifest ... >
      <!-- This attribute is "false" by default on apps targeting
           Android 10 or higher. -->
      <application android:requestLegacyExternalStorage="true" ... >
        ...
      </application>
    </manifest>
    

To test how an app targeting Android 9 or lower behaves when using scoped storage, you can opt in to the behavior by setting the value of requestLegacyExternalStorage to false.

Important: Even if you opt out of scoped storage, you need the ACCESS_MEDIA_LOCATION permission to read unredacted location information in images unless you use the File APIs directly to open file paths from MediaStore or other sources.

By opening media files using any of the following methods, you need to declare and request the ACCESS_MEDIA_LOCATION permission to view unredacted location information: