Accessing External Storage in Android API 29

Accessing external storage in Android API 29

On Android 10 Environment.getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() will return storage paths but paths are not readable or writable.

  1. For Android 10 you can continue to use paths provided by Environment.getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() if you add android:requestLegacyExternalStorage="true" to application tag in manifest file. At runtime your app can call Environment.isExternalStorageLegacy() to check if the request has been done.

  2. Another (not known) possibility (only for Android 10) is to add <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> to manifest file.
    The user has to go to the advanced settings of the app and enable from Advanced settings Install unknown apps | Allow from this source.
    The nice thing with this is that the user can switch the access rights. You can make it easier for the user if you implement an intent for
    Settings.ACTION_APPLICATION_DETAILS_SETTINGS where he can change the settings.
    A funny thing is that Environment.isExternalStorageLegacy() returns true then too.

  3. Compiling for Android 11 both options do not work on an Android 11 device. (But they continue to work for Android 10 devices). The paths of Environment.getExternalStorageDirectory() and Environment.getExternalStoragePublicDirectory() are usable again in read mode and very often in write mode too. And this is great as one can simply list the contents of directories like Download or Pictures or DCIM/Camera again using the File class.
    But adding <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> to manifest file and implementing an intent for
    Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION will give your app read/write access for all files even on removable micro sd card.
    (Finally you can remove the google ban not being able to read/write your own micro sd card on your own Android device using your own app).
    Environment.isExternalStorageManager() can be used to check if the permission is on/off.
    As long as you do not try to upload your app to the play store you are fine.

Android SDK 30, write to the root of external storage

You can target SDK 30 and add MANAGE_EXTERNAL_STORAGE permission to the manifest:

 <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />

Do note it's a dangerous permission so you'll need to request it differently, like this:

if (!Environment.isExternalStorageManager()) {
requestManageAllPermission();
return;
}
private void requestManageAllPermission() {
try {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
intent.addCategory("android.intent.category.DEFAULT");
intent.setData(Uri.parse(String.format("package:%s", getApplicationContext().getPackageName())));
startActivityForResult(intent, REQ_MANAGE_EXTERNAL);
} catch (Exception e) {

Intent intent = new Intent();
intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
startActivityForResult(intent, REQ_MANAGE_EXTERNAL);
}
}

And you need to handle the results in:

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQ_MANAGE_EXTERNAL) {
if (!Environment.isExternalStorageManager())
finish();
}
}

REQ_MANAGE_EXTERNAL is a int constant, can be any number you want, in my case its 2296

WRITE_EXTERNAL_STORAGE when targeting Android 10

A workaround is to actually ignore the warning, as it is just informational and therefore harmless. By setting maxSdkVersion to 28 no need to worry anymore.

<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage" />

Note that using the android:requestLegacyExternalStorage flag as stated in other answers is not a solution, is just a temporary patch which will no longer work at all in Android 11 (API 30), and future versions

UPDATE, to clarify the doubts and confusions shown by some developers in the comments:

  • If using the requestLegacyExternalStorage flag in Android 10 (API 29) then request the WRITE_EXTERNAL_STORAGE permission as usual.

  • The flag requestLegacyExternalStorage does nothing in Android 11 (API 30), it is completely ignored, and there is not workaround for it.

  • WRITE_EXTERNAL_STORAGE does not give any privileges in Android 11 (API 30), it does nothing at all, therefore in API 11 you need to set the maxSdkVersion to 29.

  • If in Android 10 (API 29) you are also not using requestLegacyExternalStorage then set maxSdkVersion to 28 instead of 29.

  • Starting in Android 11 (API 30), the older File API can again be used but "only" when accessing the public "shared storage" folders (DCIM, Music, etc.), or your app "private" directory. For other locations the DocumentFile API is required.

  • Consider that the File API is now much slower in Android 11 (API 30), because has been refactored becoming essentially a wrapper. This is to enforce its usage just to the allowed locations. So, is no longer a fast system file API, is just a wrapper that internally delegates the work to the MediaStore. When using the File API in Android 11 or above you should consider the performance penalty hit, as according to the Android team it will be 2 to 3 times slower than if accessing directly the MediaStore.

How to get Access to External Storage in Android 10 (Android Q)?

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>

Access files

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

 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.
}

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.

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);


Related Topics



Leave a reply



Submit