'Android 11 ACTION_OPEN_DOCUMENT_TREE: set initial URI to the Documents folder [duplicate]

using the Scoped Storage model in Android 11 I want to give the user the ability to choose a folder, starting in the documents folder:

val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI,    ???     )
startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE,null)

The problem is, how can I generate a proper URI of the phone's documents folder? (It sits just in root / ) In the official documentation, no examples are given. I really hope there are some neat constants for all the standard locations?



Solution 1:[1]

We will manupilate INITIAL_URI obtained from StorageManager..getPrimaryStorageVolume().createOpenDocumentTreeIntent().

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
    StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);

    Intent intent = sm.getPrimaryStorageVolume().createOpenDocumentTreeIntent();
    //String startDir = "Android";
    //String startDir = "Download"; // Not choosable on an Android 11 device
    //String startDir = "DCIM";
    //String startDir = "DCIM/Camera";  // replace "/", "%2F"
    //String startDir = "DCIM%2FCamera";
    String startDir = "Documents";

    Uri uri = intent.getParcelableExtra("android.provider.extra.INITIAL_URI");

    String scheme = uri.toString();

    Log.d(TAG, "INITIAL_URI scheme: " + scheme);

    scheme = scheme.replace("/root/", "/document/");

    scheme += "%3A" + startDir;

    uri = Uri.parse(scheme);

    intent.putExtra("android.provider.extra.INITIAL_URI", uri);

    Log.d(TAG, "uri: " + uri.toString());

    ((Activity) context).startActivityForResult(intent, REQUEST_ACTION_OPEN_DOCUMENT_TREE);

    return;
}

Solution 2:[2]

how can I generate a proper URI of the phone's documents folder?

Tested on :

  1. Xiaomi M2102J20SI
  2. Emulator Pixel 4 XL API 30

Function askPermission() opens the target directory.

@RequiresApi(Build.VERSION_CODES.Q)
private fun askPermission() {
    val storageManager = application.getSystemService(Context.STORAGE_SERVICE) as StorageManager
    val intent =  storageManager.primaryStorageVolume.createOpenDocumentTreeIntent()

    val targetDirectory = "WhatsApp%2FMedia%2F.Statuses" // add your directory to be selected by the user
    var uri = intent.getParcelableExtra<Uri>("android.provider.extra.INITIAL_URI") as Uri
    var scheme = uri.toString()
    scheme = scheme.replace("/root/", "/document/")
    scheme += "%3A$targetDirectory"
    uri = Uri.parse(scheme)
    intent.putExtra("android.provider.extra.INITIAL_URI", uri)
    startActivityForResult(intent, REQUEST_CODE)
}

Uri of the file will be returned in onActivityResult()

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

                    // treeUri is the Uri of the file
                    
                   // if life long access is required the takePersistableUriPermission() is used

                    contentResolver.takePersistableUriPermission(
                            treeUri,
                            Intent.FLAG_GRANT_READ_URI_PERMISSION or
                                    Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                    )

                  readSDK30(treeUri)
                }
            }
        }
    }

Function readSDK30() is used to read files & folders from Uri

  private fun readSDK30(treeUri: Uri) {
        val tree = DocumentFile.fromTreeUri(this, treeUri)!!

        thread {
            val uriList  = arrayListOf<Uri>()
            listFiles(tree).forEach { uri ->
                 
                // Collect all the Uri from here
            }
            
        }
    }

Function listFiles() returns all the files & folders in the given Uri

fun listFiles(folder: DocumentFile): List<Uri> {
            return if (folder.isDirectory) {
                folder.listFiles().mapNotNull { file ->
                    if (file.name != null) file.uri else null
                }
            } else {
                emptyList()
            }
        }

Solution 3:[3]

All the credit goes to the above @blackapp's answer!

Here is the same code in Kotlin language:

if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
    val sm =  getSystemService(Context.STORAGE_SERVICE) as StorageManager
    intent = sm.primaryStorageVolume.createOpenDocumentTreeIntent()
    //String startDir = "Android";
    //String startDir = "Download"; // Not choosable on an Android 11 device
    //String startDir = "DCIM";
    //String startDir = "DCIM/Camera";  // replace "/", "%2F"
    //String startDir = "DCIM%2FCamera";
    val startDir = "Documents"
    var uriroot = intent.getParcelableExtra<Uri>("android.provider.extra.INITIAL_URI")    // get system root uri
    var scheme = uriroot.toString()
    Log.d("Debug", "INITIAL_URI scheme: $scheme")
    scheme = scheme.replace("/root/", "/document/")
    scheme += "%3A$startDir"                        //change uri to Documents folder
    uriroot = Uri.parse(scheme)
    intent.putExtra("android.provider.extra.INITIAL_URI", uriroot)                        // give changed uri to Intent
    Log.d("Debug", "uri: $uriroot")
  
    startActivityForResult(intent, OPEN_DIRECTORY_REQUEST_CODE);
}

As some commentors mentioned, this code might break and not work in the future, which is true. However, considering Android's past, they will change the storage API anyways every other year.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 hata
Solution 2
Solution 3 hata