-1

The following code works on Android 10, but fails on Android 11. Specifically listFiles() returns null. The path does look weird, but it seems to work for other tasks rather than listing files in a dir.

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

        // Initialize a list of required permissions to request runtime
        val list = listOf<String>(
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
            Manifest.permission.MANAGE_EXTERNAL_STORAGE
        )


        ActivityCompat.requestPermissions(this, list.toTypedArray(), 123)

        val button = findViewById<Button>(R.id.button)
        button.setOnClickListener {
            val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
            intent.setFlags(FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
            intent.addFlags(FLAG_GRANT_WRITE_URI_PERMISSION)
            startActivityForResult(intent, REQUEST_DOCUMENT_TREE)
        }



    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == REQUEST_DOCUMENT_TREE) {
            if (resultCode == RESULT_OK && data != null) {
                data.data?.let { uri ->

                    val docId = DocumentsContract.getTreeDocumentId(uri)

                    val dirUri = DocumentsContract.buildDocumentUriUsingTree(uri, docId)

                    val path = DocumentsContract.findDocumentPath(getContentResolver(), dirUri)?.path
                    path?.let {
                        val files = File(path[0].replaceFirst("raw:", "", true))
                        val ret = files.listFiles()
                        Log.d("OUR_LOG", "ret is ${ret}")
                    }

                }

            }
        }
    }

}

In manifest I added

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

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />
    <application
        android:requestLegacyExternalStorage="true"
        android:preserveLegacyExternalStorage="true"
        android:allowBackup="true"
...

How to list files from dirUri on Android 11?

Thank you in advance!

Andrew
  • 71
  • 6
  • Did you change your targetSdkVersion? – Trey Cai Jan 27 '22 at 12:26
  • `DocumentsContract.findDocumentPath()` does not return a filesystem path. After all, documents do not have to be files on the filesystem. – CommonsWare Jan 27 '22 at 12:57
  • @CommonsWare It says that it returns a canonical path, I'm using dirUri recieved from ACTION_OPEN_DOCUMENT_TREE. So I'm expecting absolute path. And it works on Android 10. Am I missing something? A third party arhiving library requires List, hence the question. – Andrew Jan 27 '22 at 18:04
  • @TreyCai No, I didn't, I expect it to work on both Android 10 and Android 11. Shouldn't I? – Andrew Jan 27 '22 at 18:04
  • "It says that it returns a canonical path" -- "path" is a very generic term in computer programming. It is not referring to a filesystem path. "And it works on Android 10" -- how many device models, out of the hundreds running Android 10, did you test? And how many document providers did you test, out of the dozens of potential providers that your users will use? "A third party arhiving library requires List" -- then you will not be able to use that library, at least with `ACTION_OPEN_DOCUMENT_TREE` and the rest of the Storage Access Framework. – CommonsWare Jan 27 '22 at 18:34

2 Answers2

1

val files = File(path[0].replaceFirst("raw:", "", true)) val ret = files.listFiles()

Of course listFiles returns null as it is a path you have no access to.

You could have used File.exists() and File.canRead() before you tried to list.

Your whole approch is wrong. Do not use the File class to begin with.

DocumentsContract gives you a nice uri for the choosen tree/directory.

Now if you wanna list files build up the children uri for that tree and after that use getContentResolver() to query() that children uri. You will obtain a cursor and an uri for every file then.

blackapps
  • 8,011
  • 2
  • 11
  • 25
1

The answer of blackapps mentions one solution, but a few points are missing:

Basically you try to be the storage manager with Manifest.permission.MANAGE_EXTERNAL_STORAGE but this permission is not granted like other permissions. You have to ask the user to do this setting manually with an Intent with the action ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION - like mentioned here: https://developer.android.com/training/data-storage/manage-all-files

The permission can be checked with Environment.isExternalStorageManager(). If these permissions are set correctly, you will probably have the file-access working, but there need to be specific reasons why your app should be the Storage Manager.

Otherwise the approach mentioned by blackapps is the correct one.

By the way, the requestLegacyExternalStorage was intended for Android 10 only as a transition and is ignored in Android 11, so the DocumentsContract is the way to go.

Christian
  • 4,596
  • 1
  • 26
  • 33