19

I'm not able to access storage when building for targetSdkVersion v29.

Here is my gradle configuration:

    compileSdkVersion 29
    buildToolsVersion "29.0.2"
    ...
        minSdkVersion 15
        targetSdkVersion 29

NOTE that WRITE_EXTERNAL_STORAGE permission is granted and the same setup works fine when building for targetSdkVersion 28.

Here is my implementation:

        val outputFolder = File(baseFolder + File.separator + "Output Folder")
        if (!outputFolder.exists()) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                Files.createDirectory(outputFolder.toPath()) //This allways returns false with targetSdkVersion 29
            } else {
                if (!outputFolder.mkdirs()) {
                    Log.e("SaveRaw", "Unable to create folder for audio recording")
                }
            }
        }

        outputFile = File("$baseFolder/Output Folder/$filename")
        try {
            fileOutputStream = FileOutputStream(outputFile)
        } catch (e: FileNotFoundException) {
            e.printStackTrace() // allways throwing exception here, even if Output Folder exists 
        }

and here is the exception:

W/System.err: java.io.FileNotFoundException: /storage/emulated/0/Chirp Auto Tester/2019_10_17 10:44:43.raw: open failed: EACCES (Permission denied)
W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:496)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:235)
        at java.io.FileOutputStream.<init>(FileOutputStream.java:186)

Hope anyone has an answer, what am I missing here?

Update:

Here is where baseFolder comes from. Note that getExternalStorageDirectory is a deprecated method.

        val baseFolder: String = if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            Environment.getExternalStorageDirectory().absolutePath
        } else {
            context.filesDir.absolutePath
        }

Thanks

Dinu
  • 390
  • 1
  • 2
  • 12
  • Try using this library for premissions https://github.com/googlesamples/easypermissions – Ruben Meiring Oct 17 '19 at 10:11
  • As I already mentioned, the required permissions for writing external storage is granted and the same setup works when building with `targetSdkVersion 28`. This is not a permission issue! – Dinu Oct 17 '19 at 10:14
  • Its premission adjacent , according to the error it can not get premsiion to acess the file /storage/emulated/0/Chirp Auto Tester/2019_10_17 10:44:43.raw: open failed: EACCES *(Permission denied)*, If android 10 use scoped storage – Ruben Meiring Oct 17 '19 at 10:17
  • How do you explain that It's working when building for `targetSdkVersion 28` then? According to error, yes, it's permission related issue but not because the permissions are not granted. – Dinu Oct 17 '19 at 10:19
  • 2
    You do not have filesystem access to arbitrary locations on external and removable storage. The [removable storage](https://commonsware.com/blog/2019/10/11/storage-situation-removable-storage.html) limitation was added in Android 4.4. The [external storage limitation](https://commonsware.com/blog/2019/06/07/death-external-storage-end-saga.html) was added in Android 10. – CommonsWare Oct 17 '19 at 10:52
  • @CommonsWare Yep, by switching to `getExternalFilesDir` and using the Music Folder helps fo fix the issue, however, the files are not visible through File Manager on the phone. I'm using `MediaScannerConnection.scanFile` to sync files. – Dinu Oct 17 '19 at 11:00
  • A file manager will have no rights to work with your files in `getExternalFilesDir()`, unless perhaps it was pre-installed by the device manufacturer. Similarly, you have no access to files in the `getExternalFilesDir()` locations for other apps. – CommonsWare Oct 17 '19 at 11:03
  • Make sense. Is it possible to write on external storage so that other apps will be able to discover the files without using deprecated methods? – Dinu Oct 17 '19 at 11:08

4 Answers4

41

Starting with Android 11 the storage permission is getting revoked and developers would need to consider alternative ways of accessing the storage they need either through SAF or Media Store. For the time being, you can carry on using what you’re using by adding the following in your manifest within the application tags:

android:requestLegacyExternalStorage="true"

You might want to consider changing your minSDK to 19 and use getExternalFilesDir() to get a path that doesn’t require any permissions.

Nikos Hidalgo
  • 3,666
  • 9
  • 25
  • 39
  • 1
    Still not working but definitely looking into the right direction, now I'm getting `ENOENT (No such file or directory)` however, the directory definitely exists. `getExternalFilesDir()` sounds about the right decision to fix the issue. – Dinu Oct 17 '19 at 10:36
  • 1
    Just a quick update, I ended up using `getExternalFilesDir()` for now. – Dinu Oct 22 '19 at 10:15
3

When your app targets Build.VERSION_CODES.Q, the path returned from this method is no longer directly accessible to apps.

    val path = getExternalFilesDir(Environment.DIRECTORY_PICTURES.toString() + 
    File.separator + "Output Folder")
    val file = File(getExternalFilesDir(null), "filename.jpg")

For more details visit Docs

Praveen
  • 430
  • 3
  • 11
1

Android 11 will simply ignore android:requestLegacyExternalStorage="true", since it was an ad-hoc solution for Android < 11 to not break old apps.

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

and also in application tag of manifest file :

 android:requestLegacyExternalStorage="true"

also declare provider in application tag of manifest file like this :

 <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/provider_path" />
    </provider>

create a folder name xml in res folder. refer to this image

now create a xml file named provider_path.xml in xml folder you just created and paste below code in xml file :

<?xml version="1.0" encoding="utf-8"?>

<path>
<external-path
    name="external_files"
    path="." />

now in your activity :

File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/"+"sample.pdf");
                    if(file.exists()){
                        Uri uri = FileProvider.getUriForFile(context, "com.example.www"+".provider",file);
                        Intent i = new Intent(Intent.ACTION_VIEW);
                        i.setDataAndType(uri, "application/pdf");
                        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP|Intent.FLAG_GRANT_READ_URI_PERMISSION);
                        context.startActivity(i);
                    }

replace com.example.www with your application package name.

MANISH
  • 2,883
  • 4
  • 11
  • 30
0

With Android version 10 (API level 29) the concept of scoped storage is introduced. According to Docs- "To give users more control over their files and to limit file clutter, apps that target Android 10 (API level 29) and higher are given scoped access into external storage, or scoped storage, by default. Such apps have access only to the app-specific directory on external storage, as well as specific types of media that the app has created."

for Android version 10, you have option to opt-out scoped storage by adding android:requestLegacyExternalStorage="true" in AndroidManifest.xml within the application tags

This is a temporary solution and will work only for android version 10 and not above it. Also make sure you ask run-time permission for read/write external storage.

mohit arora
  • 592
  • 6
  • 10