Android 11 Scoped storage permissions
Asked Answered
H

16

117

My App use the file paths of images provided by Environment.getExternalStorageDirectory() to create albums of photos, but with Android 11 I won't be able to access directly files.

According to the Android developers documentation they recently introduced the MANAGE_EXTERNAL_STORAGE permission, but I didn't understand if adding this permission I'm able to continue to access file by Environment or not.

I tried my application on an Android 11 Virtual device and it seems to work perfectly even without requesting the MANAGE_EXTERNAL_STORAGE permission!

Reading the documentation on Android Developers, it seems that the applications that uses the File API for accessing Photos and Medias only locations can continue to work, but I'am not sure.

Is there anyone who better understood the Android Documentation???

Hauge answered 7/7, 2020 at 19:35 Comment(4)
@blackapps there is no android 11 emulator or phone yet.Lareine
O yes. Some months already. Android Studio has that for all Pixel emulators. Update your sdk. On Android 11 you get again access to external storage by default.Adamandeve
Watch out: it works for me on the emulator, but on physical devices! Very treacherous.Carmelinacarmelita
See following links : https://mcmap.net/q/89429/-how-do-i-compress-image-in-android-studio-java-and-store-it-in-device-specific-folder https://mcmap.net/q/89430/-android-9-api-29-storage-emulated-0-pictures-mypic-png-open-failed-eacces-permission-denied https://mcmap.net/q/89431/-environment-getexternalstoragedirectory-deprecated-in-api-level-29-java do not use manage external storage permissionEchovirus
D
161

Android 11

If you are targeting Android 11 (targetSdkVersion 30) then you require the following permissions in AndroidManifest.xml for modifying and document access.

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

For Android 10 you place the following line in your AndroidManifest.xml tag

android:requestLegacyExternalStorage="true"

the method below checks if the permission is allowed or denied

private boolean checkPermission() {
    if (SDK_INT >= Build.VERSION_CODES.R) {
        return Environment.isExternalStorageManager();
    } else {
        int result = ContextCompat.checkSelfPermission(PermissionActivity.this, READ_EXTERNAL_STORAGE);
        int result1 = ContextCompat.checkSelfPermission(PermissionActivity.this, WRITE_EXTERNAL_STORAGE);
        return result == PackageManager.PERMISSION_GRANTED && result1 == PackageManager.PERMISSION_GRANTED;
    }
}

The below method can be used for requesting a permission in android 11 or below

private void requestPermission() {
    if (SDK_INT >= Build.VERSION_CODES.R) {
        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, 2296);
        } catch (Exception e) {
            Intent intent = new Intent();
            intent.setAction(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
            startActivityForResult(intent, 2296);
        }
    } else {
        //below android 11
        ActivityCompat.requestPermissions(PermissionActivity.this, new String[]{WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE);
    }
}

Handling permission callback for Android 11 or above versions

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 2296) {
        if (SDK_INT >= Build.VERSION_CODES.R) {
            if (Environment.isExternalStorageManager()) {
                // perform action when allow permission success
            } else {
                Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Handling permission callback for OS versions below Android 11

@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    switch (requestCode) {
        case PERMISSION_REQUEST_CODE:
            if (grantResults.length > 0) {
                boolean READ_EXTERNAL_STORAGE = grantResults[0] == PackageManager.PERMISSION_GRANTED;
                boolean WRITE_EXTERNAL_STORAGE = grantResults[1] == PackageManager.PERMISSION_GRANTED;

                if (READ_EXTERNAL_STORAGE && WRITE_EXTERNAL_STORAGE) {
                    // perform action when allow permission success
                } else {
                    Toast.makeText(this, "Allow permission for storage access!", Toast.LENGTH_SHORT).show();
                }
            }
            break;
    }
}

NOTE: MANAGE_EXTERNAL_STORAGE is a special permission only allowed for few apps like Antivirus, file manager, etc. You have to justify the reason while publishing the app to PlayStore.

Deposit answered 25/2, 2021 at 9:46 Comment(15)
This works fine. But what is the equivalent of Environment.getExternalStorageDirectory()? It is deprecated.Pyrexia
If you use api 29 don't need this permission. This permission is for API 30 or above.Deposit
When I submit to playstore I get : Your APK or Android App Bundle requests the 'android.permission.MANAGE_EXTERNAL_STORAGE' permission, which Google Play doesn't support yet.Echovirus
@AhamadullahSaikat use MediaStore.Files.getContentUri("external") to access all filesGamecock
can you at least explain what in the world is 2296 ???????Mansized
it's request code for requesting permissionDeposit
@Mansized it is a random number for request code and response. you ask for permission with this code and check if that is the response. you can put other number in all places.Hush
Google rejected my app after Implementing this solution.Widdershins
@YogeshNikamPatil Then what's an alternative you used to publish your app with android.permission.MANAGE_EXTERNAL_STORAGE .Cleveland
@YogeshNikamPatil just use context.getExternalFilesDir(null) or context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) if targeting SDK 30, there is no need for write_external_storage or MANAGE_EXTERNAL_STORAGE at all. This "getExternalFilesDir" is returning path of app-specific folder on external storage which does not require any permission. Those permissions are only needed if you want to modify public shared external storage, and app-specific folder isn't thatAntitoxin
this is risky permission because google allow this permission for some apps only like file manager, antivirus apps and etc. read policy program carefullyDeposit
Shouldn't it be android:maxSdkVersion="29" in manifest instead of android:maxSdkVersion="28"? We still need write permission to work on SDK 29 if we use manage storage permission for SDK 30+.Credit
Thanks this very nice answer can you please help me what , how and where we have to justify MANAGE_EXTERNAL_STORAGE permission do we have to upload video or we have to write any document ?Ashurbanipal
Read this article developer.android.com/training/data-storage/…Deposit
I have created file like this and its working without MANAGE_EXTERNAL_STORAGE permission. https://mcmap.net/q/89432/-how-to-create-folder-android-r-api-30Unbowed
I
38

Android 11 doesn't allow to access directly files from storage you must have to select file from storage and copy that file into your app package chache com.android.myapp. Below is the method to copy file from storage to app package cache

private String copyFileToInternalStorage(Uri uri, String newDirName) {
    Uri returnUri = uri;

    Cursor returnCursor = mContext.getContentResolver().query(returnUri, new String[]{
            OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE
    }, null, null, null);


    /*
     * Get the column indexes of the data in the Cursor,
     *     * move to the first row in the Cursor, get the data,
     *     * and display it.
     * */
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
    returnCursor.moveToFirst();
    String name = (returnCursor.getString(nameIndex));
    String size = (Long.toString(returnCursor.getLong(sizeIndex)));

    File output;
    if (!newDirName.equals("")) {
        File dir = new File(mContext.getFilesDir() + "/" + newDirName);
        if (!dir.exists()) {
            dir.mkdir();
        }
        output = new File(mContext.getFilesDir() + "/" + newDirName + "/" + name);
    } else {
        output = new File(mContext.getFilesDir() + "/" + name);
    }
    try {
        InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
        FileOutputStream outputStream = new FileOutputStream(output);
        int read = 0;
        int bufferSize = 1024;
        final byte[] buffers = new byte[bufferSize];
        while ((read = inputStream.read(buffers)) != -1) {
            outputStream.write(buffers, 0, read);
        }

        inputStream.close();
        outputStream.close();

    } catch (Exception e) {

        Log.e("Exception", e.getMessage());
    }

    return output.getPath();
}
Indeterminate answered 13/1, 2021 at 9:52 Comment(4)
not require MANAGE_ALL_FILES permission from playstore ?Rattish
change android:maxSdkVersion 28 to 29 in user-permissionCotton
What is the value of Uri in copyFileToInternalStorage method?Mistrustful
i got error Attempt to invoke interface method 'int android.database.Cursor.getColumnIndex(java.lang.String)' on a null object referenceBinghi
C
23

Review Android 11 Scoped Storage Updates here

Quick Solution is here :

For Quick Solution if you put your android target and compile sdk version is 29 then your app will run on android 11 with the same implementation as u did on android ten here

gradle is here

In mainfest file

in mainfest

When you updating your android Device from api 10(29) to android 11(30) Api , its not working to retrieve data from your device storage or mobile directory i have checked today on play store thousand of the apps having millions download live on play store they are not working on android 11 , because android 11 introduced new scoped storages update where you have to implement new methods to get media file using MediaStore Object,

some useful information that i wants to share with you after reading the android documentation are listed here:

in android 11 , you can access the cache only for their own specific apps.

apps cannot create their own app-specific directory on external storage. To access the directory that the system provides for your app, call getExternalFilesDirs()

If your app targets Android 11, it cannot access the files in any other app's data directory, even if the other app targets Android 8.1 (API level 27) or lower and has made the files in its data directory world-readable

On Android 11, apps can no longer access files in any other app's dedicated, app-specific directory within external storage.

Apps that run on Android 11 but target Android 10 (API level 29) can still request the requestLegacyExternalStorage attribute. This flag allows apps to temporarily opt out of the changes associated with scoped storage, such as granting access to different directories and different types of media files. After you update your app to target Android 11, the system ignores the requestLegacyExternalStorage flag.

before this on android 10 we were using

 android:requestLegacyExternalStorage="true"
    tools:targetApi="q"

in manifest under application attribute now this method is not working in android 11.

so migrate to the new updates now thanks

Review Here Scoped Storage Updates

follow the tutorial guidelines here Follow the Scoped Storage tutorial at GitHub

Custodian answered 9/2, 2021 at 7:41 Comment(2)
there are two solutions if you will target your app for api 30 then you can not used requestlegacyExternalstorage permission but you can use this only for android 29 and your app will run on 30 api too .. second solution use media store api and manage request permission and remove requestlegacyexternal permission.Custodian
Not a valid answer now, We must have to use updated targetSdkVersion in order to publish the app on the play store at least 31. check documentation - developer.android.com/google/play/requirements/target-sdkDuce
B
20

According to the Android developers documentation they recently introduced the MANAGE_EXTERNAL_STORAGE permission, but I didn't understand if adding this permission I'm able to continue to access file by Environment or not.

Yes, you will. However, bear in mind that if you intend to distribute your app on the Play Store (and perhaps elsewhere), you will need to justify the reason for requesting that permission. So, unless you have a very good reason to use MANAGE_EXTERNAL_STORAGE, please use something else.

Bilateral answered 7/7, 2020 at 20:39 Comment(5)
It looks that for the now actual 30 version this does not come true. Adding to the manifest does nothing. In the settings for the app nothing can be done. Has something to be done at runtime? To my surpise there is, by default, read access for external storage which was not the case on 29. Using Pixel emulator api 30.Adamandeve
@blackapps: "Adding to the manifest does nothing" -- adding what? If you mean MANAGE_EXTERNAL_STORAGE, it worked for me on a Pixel 2 the last time I tried it. "In the settings for the app nothing can be done" -- there was for me, when launching Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION. See gitlab.com/commonsguy/cw-android-r/-/tree/v0.3/RawPaths. "To my surpise there is, by default, read access for external storage which was not the case on 29" -- that is "raw paths". Most of external storage is covered, though not all.Bilateral
Indeed that all files access action is needed and works. Even the whole sd card is suddenly writable. Thanks. I meant adding <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> to manifest.Adamandeve
@Bilateral File createFile = new File(getApplicationContext().getExternalFilesDir(null)+File.separator+"My App"); Is app specific Storage (Private storage) required WRITE_EXTERNAL_STORAGE Permission?Fierro
@MDev: No, you do not need permissions to use the directories available from methods on Context, such as getExternalFilesDir().Bilateral
H
16

I found this way for Android 11 (SDK R - 30):

1- In Manifest must add this permission: (just for R)

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

2- Request the OS dialogue to ask for permission:

ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.READ_EXTERNAL_STORAGE,
                                Manifest.permission.MANAGE_EXTERNAL_STORAGE}, 1);

3- Check your app can access to the storage :

if (!Environment.isExternalStorageManager()) 

4- Use Intent to open the "All Files Access " for your app

Intent intent = new Intent();
                    intent.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                    Uri uri = Uri.fromParts("package", this.getPackageName(), null);
                    intent.setData(uri);
                    startActivity(intent);

enter image description here

enter image description here

Harim answered 17/4, 2021 at 15:47 Comment(5)
When I submit to playstore I get : Your APK or Android App Bundle requests the 'android.permission.MANAGE_EXTERNAL_STORAGE' permission, which Google Play doesn't support yet.Echovirus
@Echovirus ,Yes you are right. This permission will be available after 5th May as google mentioned it.Harim
support.google.com/googleplay/android-developer/answer/10467955Cabanatuan
@Akito please see this page: developer.android.com/training/data-storage/…Harim
In android 10 and above we can't use MANAGE_EXTERNAL_STORAGE for play store app submission unless the app is a file manager or antivirus. Google will reject your app.Hierodule
D
7

Note: This answer does not require MANAGE_EXTERNAL_STORAGE permission

In android 10 and above MANAGE_EXTERNAL_STORAGE we can't use it for play store applications unless it is file manager or antivirus that makes it pretty useless.

so to access photos from storage without MANAGE_EXTERNAL_STORAGE below answer would be useful

In Manifest

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28" />

To access media files

// Need the READ_EXTERNAL_STORAGE permission if accessing video files that your
// app didn't create.

// Container for information about each video.


data class Image(val uri: Uri,
    val name: String,
    val duration: Int,
    val size: Int
)
val imgList = mutableListOf<Image>()

val collection =
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        MediaStore.Images.Media.getContentUri(
            MediaStore.VOLUME_EXTERNAL
        )
    } else {
        MediaStore.Images.Media.EXTERNAL_CONTENT_URI
    }

val projection = arrayOf(
    MediaStore.Images.Media._ID,
    MediaStore.Images.Media.DISPLAY_NAME,
 
   MediaStore.Images.Media.SIZE
)



// Display videos in alphabetical order based on their display name.
val sortOrder = "${MediaStore.Images.Media.DISPLAY_NAME} ASC"

val query = ContentResolver.query(
    collection,
    projection,
   null,
   null,
    sortOrder
)
query?.use { cursor ->
    // Cache column indices.
    val idColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID)
    val nameColumn =
            cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)
 
    val sizeColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.SIZE)

    while (cursor.moveToNext()) {
        
        val id = cursor.getLong(idColumn)
        val name = cursor.getString(nameColumn)
     
        val size = cursor.getInt(sizeColumn)

        val contentUri: Uri = ContentUris.withAppendedId(
           MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
            id
        )

        // Stores column values and the contentUri in a local object
        // that represents the media file.
       imgList += Image(contentUri, name, size)
    }
}

To create a file

// Request code 
const val CREATE_FILE = 1

private fun createFile(pickerInitialUri: Uri) {
    val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "Type of file"
        putExtra(Intent.EXTRA_TITLE, "Name of File")

  // Optionally, specify a URI for the directory that should be  opened in
    
  // the system file picker before your app creates the document.
        putExtra(DocumentsContract.EXTRA_INITIAL_URI, pickerInitialUri)
    }
    startActivityForResult(intent, CREATE_FILE)
}
Derose answered 18/9, 2021 at 10:7 Comment(2)
Where do you put the actuale file information, like if i were to make a .txt file with createfile?Nationalism
@J. M. You have to override onActivityResult(). In this method you can write content of the file.Derose
A
4

In Android 11 This is my fully functioning Code to get a Camera up and running:

<!--Still need to request legacy storage for devices running on API 29 and below otherwise they won't work -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yourpackage">

<!-- For Various Types -->
<queries>
    <intent>
        <action android:name="android.intent.action.SEND" />
        <data android:mimeType="vnd.android.cursor.dir/email" />
    </intent>
    <intent>
        <action android:name="android.media.action.IMAGE_CAPTURE" />
    </intent>
    <intent>
        <action android:name="android.intent.action.CALL" />
    </intent>
</queries>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<!-- ... Rest of manifest -->

<application
...
android:requestLegacyExternalStorage="true"
...
   <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths">
        </meta-data>
    </provider>
</application>
</manifest

The file_path.xml document goes in the res/xml folder and contains the following for pictures:

<paths xmlns:android="http://schemas.android.com/apk/res/android">

<external-files-path
    name="internal_images"
    path="files/Pictures" />
<external-files-path
    name="internal_images_alternate"
    path="Pictures" />
</paths>

Then when actually checking for storage options I implemented the following piece of code:

private boolean hasManageExternalStoragePermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        if (Environment.isExternalStorageManager()) {
            return true;
        } else {
            if (Environment.isExternalStorageLegacy()) {
                return true;
            }
            try {
                Intent intent = new Intent();
                intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(Uri.parse("package:com.example.yourpackage"));
                startActivityForResult(intent, RESULT_CODE); //result code is just an int
                return false;
            } catch (Exception e) {
                return false;
            }
        }
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        if (Environment.isExternalStorageLegacy()) {
            return true;
        } else {
            try {
                Intent intent = new Intent();
                intent.setAction(ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
                intent.setData(Uri.parse("package:com.example.yourpackage"));
                startActivityForResult(intent, RESULT_CODE); //result code is just an int
                return false;
            } catch (Exception e) {
                return true; //if anything needs adjusting it would be this
            }
        }
    }
    return true; // assumed storage permissions granted
}

Next for the permission request:

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MANAGE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permission request code is just an int
        } else {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, PERMISSION_REQUEST_CODE); //permisison request code is just an int
        }

Then (and I know this is out of scope for the original question) you have the prospect of using the camera intent which goes like this now:

public static Intent getCameraIntentWithUpdatedPackages(Context context){
    List<ResolveInfo> resolveInfo = new ArrayList<>();

    final Intent capturePhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    PackageManager pm = context.getPackageManager();
    resolveInfo = pm.queryIntentActivities(capturePhoto, 0);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R){
        // For Android 11 we need to add specific camera apps
        // due them are not added during ACTION_IMAGE_CAPTURE scanning...
        resolveInfo.addAll(getCameraSpecificAppsInfo(context));
    }
    return capturePhoto;
}

private static List<ResolveInfo> getCameraSpecificAppsInfo(Context context){
    List<ResolveInfo> resolveInfo = new ArrayList<>();
    if (context == null){
        return resolveInfo;
    }
    PackageManager pm = context.getPackageManager();
    for (String packageName : CAMERA_SPECIFIC_APPS) {
        resolveInfo.addAll(getCameraSpecificAppInfo(packageName, pm));
    }
    return resolveInfo;
}

private static List<ResolveInfo> getCameraSpecificAppInfo(String packageName, PackageManager pm){
    Intent specificCameraApp = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    specificCameraApp.setPackage(packageName);
    return pm.queryIntentActivities(specificCameraApp, 0);
}


public static File dispatchTakePictureIntent(Context context, String photoNameSuffix) {
   
        Intent takePictureIntent = getCameraIntentWithUpdatedPackages(context);
        // Ensure that there's a camera activity to handle the intent
        if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
            // Create the File where the photo should go
            File photoFile = null;
            try {
                photoFile = createImageFile(activity, photoNameSuffix);
            } catch (IOException ex) {
                ex.printStackTrace();
            }
            // Continue only if the File was successfully created
            if (photoFile != null) {
                Uri photoURI = Uri.fromFile(photoFile);
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
                } else {
                    File file = new File(photoURI.getPath());
                    if (!file.exists()) {
                        file.mkdirs();
                        file.mkdir();
                        try {
                            file.createNewFile();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    Uri photoUri = FileProvider.getUriForFile(context.getApplicationContext(), context.getApplicationContext().getPackageName() + ".provider", file);
                    activity.grantUriPermission(photoURI.getAuthority(), photoUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
                }
                //disable strict mode policies
                StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
                StrictMode.setVmPolicy(builder.build());
                context.startActivityForResult(takePictureIntent, REQUEST_TAKE_PHOTO);
            }
            return photoFile;
        }
    
    return null;
}

static final String[] CAMERA_SPECIFIC_APPS =  new String[]{
        "best.camera",
        "net.sourceforge.opencamera",
        "com.google.android.GoogleCamera",
        "tools.photo.hd.camera",
};

And just like that we have a picture we can rename into our own directory assuming the package name is granted all files access!

Angelia answered 19/4, 2021 at 18:41 Comment(5)
I tested this on Android Level 30 and it works without the request legacy storage but without the request legacy storage android 29 breaks still...Angelia
When I submit to playstore I get : Your APK or Android App Bundle requests the 'android.permission.MANAGE_EXTERNAL_STORAGE' permission, which Google Play doesn't support yet.Echovirus
@Vishalkumarsinghvi downgrade target to 29 from 30.. that works and remove the code for 30... and wait for google to allow.Echovirus
Can we still use READ and WRITE_EXTERNAL_STORAGE permissions, if our app target is 29? As I understand those permissions should be replaced with MANAGE_EXTERNAL_STORAGE only for apps targeted 30? so untill google allows to push apps with target 29, we can still use them?Unsuitable
When Google Allows the new API level 30 (Which is coming up May 5th), then this code will fully upgrade your app. Until then go to API level 29 with the request legacy flag.Angelia
D
4

MANAGE_EXTERNAL_STORAGE is strict permission and should be used for valid purposes only, e.g. file manager and anti-virus apps. See the usage.

I would offer something simpler with this library. You can access the scoped storage without the full disk permission (MANAGE_EXTERNAL_STORAGE). This code will ask the user to grant the access:

class MainActivity : AppCompatActivity() {

    private val storageHelper = SimpleStorageHelper(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        setupSimpleStorage(savedInstanceState)
        setupButtonActions()
    }

    private fun setupButtonActions() {
        btnRequestStorageAccess.setOnClickListener {
            storageHelper.requestStorageAccess()
        }

        btnSelectFolder.setOnClickListener {
            storageHelper.openFolderPicker()
        }

        btnSelectFile.setOnClickListener {
            storageHelper.openFilePicker()
        }
    }

    private fun setupSimpleStorage(savedInstanceState: Bundle?) {
        savedInstanceState?.let { storageHelper.onRestoreInstanceState(it) }
        storageHelper.onStorageAccessGranted = { requestCode, root ->
            Toast.makeText(this, "Yay, granted!", Toast.LENGTH_SHORT).show()
        }

        storageHelper.onFileSelected = { requestCode, file ->
            Toast.makeText(this, file.fullName, Toast.LENGTH_SHORT).show()
        }

        storageHelper.onFolderSelected = { requestCode, folder ->
            Toast.makeText(this, folder.getAbsolutePath(this), Toast.LENGTH_SHORT).show()
        }
    }
}

Since direct file paths (java.io.File) are no longer reliable, thus you need DocumentFile which manages files via URI. The library also provides rich extension functions, i.e.:

  • DocumentFile.getProperties()
  • DocumentFile.search()
  • DocumentFile.deleteRecursively()
  • DocumentFile.openOutputStream()
  • DocumentFile.copyFileTo()
  • List<DocumentFile>.moveTo(), etc.
Divebomb answered 14/7, 2021 at 19:4 Comment(0)
V
2

If you want to write and read files from the device. You can basically use Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)(it doesn't have to be DIRECTORY DOCUMENTS) instead of Environment.getExternalStorageDirectory(), you don't need to ask for MANAGE_EXTERNAL_STORAGE permission. It is working normally on Android 11 in this way.

Ventail answered 12/10, 2021 at 8:23 Comment(1)
This is currently working for me, however Environment.getExternalStoragePublicDirectory is deprecated in API level 29.Hofuf
O
1

I have solved the issue -

Do -

  1. Save in the external directory as this will help to read in SDK version 30 or above.
  2. Add '//' + directory path & your problem will be resolved So it means that your path will be '//'+ getExternalStorageDirectory())!.path
  3. Add read and write permission - In Manifest

to access media files

Don't use this as your app will not be accepted in the play store.

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

This is the code to save and retrieve the file & it works on both SDK > 30 and SDK =< 30.

final directory = (await getExternalStorageDirectory())!.path;
  ByteData? byteData =
      await (image.toByteData(format: ui.ImageByteFormat.png));
  Uint8List pngBytes = byteData!.buffer.asUint8List();
  File imgFile = new File('$directory/screenshot${rng.nextInt(2000)}.png');
  await imgFile.writeAsBytes(pngBytes);
  setState(() {
    _imageFile = imgFile;
  });
  // Add '//' + directory path & your problem will be resolved
  return '//'+imgFile.path; 

Now share the file - takeScreenshot().then((value) => Share.shareFiles(['$value'], text: 'Hello'), );

Overton answered 5/10, 2021 at 0:12 Comment(1)
This is at best half a solution if you want to be on the play store: support.google.com/googleplay/android-developer/answer/…Chuckchuckfull
R
0

I also looked for a solution for several hours and tested some approaches. In my app, users can send emails with an pdf document as attachment and suddenly since android 11 the attachment was empty due to the permission changes of android. For getting the file I use a FileProvider. The suggested methods I found here but also in other threads didn't work, until I tested around for my own and casually did the same as Monu meena and added this in my android manifest file:

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

This is the only working solution in my case.I didn't remove read or write permissions and I also didn't set target sdk to 29 or lower, my target sdk is still 30 and it is also working for devices with API lower than 30. I have tested successfully on several devices with different API version in android studio emulator.

So give it a try, sometimes the simplest solutions are the best.

Restricted answered 23/6, 2021 at 6:55 Comment(1)
Google strongly restricts use of this permission so if you want to be on the play store this is likely going to get your app flagged: support.google.com/googleplay/android-developer/answer/…Chuckchuckfull
E
0

In File Location (wherever you are using it) use

mContext.getExternalFilesDir(Environment.DIRECTORY_PICTURES);

instead of

Environment.getExternalStorageDirectory();

And in permissions use (see comment out of permission)

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            // return Environment.isExternalStorageManager();
            return true;
        }
Echovirus answered 29/9, 2021 at 0:4 Comment(0)
H
0

In my case just need was create an .csv file then send to server i had follow this document.

This document.

 File folder  = new File(getBaseContext().getFilesDir(), "/test/CSV");
 filepath = folder.toString() + "/" + id + ".csv";
Horseback answered 15/11, 2021 at 11:56 Comment(0)
M
0
private fun loadFilesFromSharedStorage() {
    try {
        val projection = arrayOf(
            MediaStore.MediaColumns._ID,
            MediaStore.MediaColumns.DISPLAY_NAME
        )
        val selection = when (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
            true -> "${MediaStore.MediaColumns.RELATIVE_PATH} LIKE ?"
            else -> MediaStore.Images.Media.DATA + " like ? "
        }
        val selectionArgs = arrayOf("%test%") 
        val uriExternal = MediaStore.Files.getContentUri("external")
        contentResolver.query(
            uriExternal,
            projection,
            selection,
            selectionArgs,
            null
        )?.use {
            val idColumn = it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)
            while (it.moveToNext()) {
                try {
                    val contentUri: Uri = ContentUris.withAppendedId(
                        uriExternal,
                        it.getLong(idColumn)
                    ) /*Use this URI for next*/
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
    } catch (e: Exception) {
        e.printStackTrace()
    }

}

Use block to fetch files Shared Storage in Android11 and use it

Matadi answered 19/2, 2022 at 16:44 Comment(0)
E
0

Access to non-media files on Android 11+

On Android 11+, non-media files (like txt, pdf, csv, ...) can be accessed if and only if

the text files are in the → ASD or in the → Private dir (i.e. were saved there), or the text files are in one of the Shared folders /Documents or /Download and these files were created by the app itself, or SAF is used, or MANAGE_EXTERNAL_STORAGE* permission is requested and granted. *` This is not really an option, because an app that requests this permission will (most likely) not be allowed in Google's Play Store.

Note: Of course, all files in the assets can be read. However these are read only.

Access to media files on Android 11+

Without READ permission, media files (like jpg, png, mp3, mp4, ...) can be read on Android 11+ if and only if

these files are located in one of the Shared folders (/Pictures, /DCIM, /Documents, /Download, /Music, /Movies) and were created by the app itself, or these are in the ASD or these are in the Private directory. With READ permission, all media files can be read from all areas of the external storage (also from the root dir).

Without WRITE permission, media files can be saved on Android 11+ if and only if

these are stored in the ASD or these are stored in Private dir or these are stored in one of the Shared folders. The Private directory is in the internal storage:

/data/user/0//files/ Note: Since WRITE_EXTERNAL_STORAGE permission no longer exists on Android 11+, without MANAGE_EXTERNAL_STORAGE there is no way to save files outside of the ASD, Private dir or Shared folders. Or you must use SAF.

Eve answered 28/10, 2023 at 6:59 Comment(0)
B
-3

Ins simple way only we need to enable below permission

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

then we can use our old codes to perform any action

Boehmenist answered 7/5, 2021 at 6:1 Comment(1)
but if you want to get accepted by the play store there's going to be some issues when you submit this app, so this is at best half a fix. See support.google.com/googleplay/android-developer/answer/…Chuckchuckfull

© 2022 - 2024 — McMap. All rights reserved.