Search code examples
androidandroid-11storage-access-frameworkscoped-storage

Android 11 - Scoped storage - unable to use java.io.File even after Uri permissions are granted


I'm currently migrating an Android app to Scoped Storage. Moving away from java.io.file is a real nightmare, and feels much of a drawback to me. I'm currently trying to understand if paths returned by getExternalMediaDirs() can effectively be used with direct file paths.

After asking user to select a given folder

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uriToLoad);
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);

and storing persistent Uri permissions to read/write in onActivityResult

getContentResolver().takePersistableUriPermission(data.getData(), (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION));

Is it expected to still not being able to use java.io.File in such granted path?

SAF path:

content://com.android.externalstorage.documents/tree/primary%3AAndroid/media/com.otherapp/test.txt

equivalent to java.io.File path

/storage/emulated/0/Android/media/com.otherapp/test.txt

Some final notes:

  • such path can be written with SAF
  • such path cannot be written with direct java.io.File path (EACCES exception)
  • /Android/media/com.otherapp is different than self-attributed /Android/media/com.app
  • android.permission.MANAGE_EXTERNAL_STORAGE not available

thanks nicola


Solution

  • Yes, because after granting permissions you have access to Content Scheme Uri of that specific folder not file system paths so you will be unable to list java.io.File objects using this Uri.

    But you can use it to listFiles() which will return you a list of DocumentFile. You can then use these DocumentFile objects to get Uri of that specific file using documentFile.getUri() . Using that Uri you can easily perform basic operations like display,copy,delete,share on that file.

    Below is an example of listing files from a folder of other app.

    I am skiping take permissions part of code assuming that you already have permissions to access that other app's folder

    In this example i am listing files from Android/media/com.whatsapp/WhatsApp/Media/.Statuses

    DOC Ids are

    companion object {
        const val ANDROID_DOCID = "primary:Android/media/"
        const val EXTERNAL_STORAGE_PROVIDER_AUTHORITY = "com.android.externalstorage.documents"
        private val androidUri = DocumentsContract.buildDocumentUri(
            EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
        )
        val androidTreeUri = DocumentsContract.buildTreeDocumentUri(
            EXTERNAL_STORAGE_PROVIDER_AUTHORITY, ANDROID_DOCID
        )
    }
    

    After getting permission we have tree Uri

    Here tree Uri is content://com.android.externalstorage.documents/tree/primary:Android/media

    val dir = DocumentFile.fromTreeUri(
        context,
       treeUri!!
    )
    

    This dir is a DocumentFile, now using this to list all folders inside that tree Uri.

    val selectedPackageName = “com.whatsapp”
    val FOLDER_NAME_STATUSES = ".Statuses”;
    val selectedRootName = “WhatsApp"
    var waStatus: DocumentFile? = null
    val statusesFiles = arrayListOf<DocumentFile>()
    dir?.listFiles()?.forEach {
        if (it.name.equals(selectedPackageName)) {
            it.listFiles().forEach {
                if (it.name.equals(selectedRootName)) {
                    it.listFiles().forEach {
                         if (it.name.equals(Constants.FOLDER_NAME_STATUSES)) {
                                    waStatus = it
                             statusesFiles.addAll(waStatus!!.listFiles().filter {
                                        it.mimeType.equals(Constants.MIME_TYPE_IMG_PNG) || it.mimeType.equals(
                                            Constants.MIME_TYPE_IMG_JPG
                                        ) || it.mimeType.equals(Constants.MIME_TYPE_IMG_JPEG)
                                    }.sortedByDescending { it.lastModified() })
    
                                }
    
                            }
    
    
                        }
                    }
                }
            }
    

    Now we have a list of DocumentFile from folder of another app. Use Uri of this file for further operations like display,copy,delete,share.

    Also, read comments your point about Downloads folder but would this be the same matter for files in Download

    Downloads is a public directory, i listed files which are created by my own app using listFiles() and it returned me list of File.

     val folder = File(
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
            Constants.DOWNLOADS_APP_DIRECTORY_NAME
        )
          if (folder != null && folder.exists()) {
                    val newFiles: Array<File> = statusFolderInDownloads.listFiles({ file ->
                        getFileType(file.path) == FILETYPE.IMAGE || getFileType(file.path) == FILETYPE.VIDEO
                    })
                    list.addAll(newFiles)
                    list.sortByDescending { it.lastModified() }
                    if (list.isNotEmpty()) {
                        return list
                    } else {
                        return arrayListOf()
                    }
                }