Android 11 scoped Storage problem — One possible solution

Android 11 introduced some several “improvements” to the external storage. If you didn’t know about this, you should read this article:

https://developer.android.com/about/versions/11/privacy/storage

The documentation points that:

“The release also offers improvements to scoped storage, which makes it easier for developers to fulfill their storage use cases after they migrate to using this storage model.”

OK, now let’s talk seriously…this “improvements” are just breaking our apps, this is because you cannot access a lot of directories that we were using on previous android versions. Thank you Android development team, we appreciate this really helpful features (high sarcasm meter values).

My app situation

My app needs to write inside the Android/data folder, and this is not possible with android 11. With Android 10 (API level 29) you can opt out of scoped storage using this value inside the manifest:

<manifest>
    <application android:requestLegacyExternalStorage="true">
    </application>
</manifest> 

Great solution, but what about Android 11 (API 30)?

After hours of research (Google, Stack Overflow, etc), I ended up with a solution to this. It is not so “aesthetic” but it works so I will explain it in the following section.

The solution

To allow writing to the Android/data folder and subfolders (or any folder inside Android/), first we have to request permissions to the user. The user chooses the folder and then we should verify that the chosen folder is the correct one. To achieve this we can use the method startActivityForResult() with the Intent “OPEN_DOCUMENT_TREE”. Code:

startActivityForResult((new Intent("android.intent.action.OPEN_DOCUMENT_TREE")).putExtra("android.provider.extra.INITIAL_URI", (Parcelable)DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Android")), 555);

After we execute this code, the user has to choose the folder, without any navigation, just clicking the button “USE THIS FOLDER”.

And after he chooses the folder, the following dialog is displayed:

Now we have to:

  • Grab the result by using the method onActivityResult().
  • Get the Uri and make it persistent across device reboots by using the method takePersistableUriPermission().
public void onActivityResult(int paramInt1, int paramInt2, Intent paramIntent) {
super.onActivityResult(paramInt1, paramInt2, paramIntent);
if (paramInt1 == 555 && paramInt2 == -1) {
if(paramIntent.getData().toString().equals("content://com.android.externalstorage.documents/tree/primary%3AAndroid")) {
Log.d("EXEC", "EXEC");
this.permUri = paramIntent.getData();
this.requireActivity().getContentResolver().takePersistableUriPermission(paramIntent.getData(), Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
}

You can retrieve the persisted Uri permissions with the method: getContentResolver().getPersistedUriPermissions() and perform a check before trying to request permission again. So it will be something like this:

for (UriPermission p :getContentResolver().getPersistedUriPermissions()){    Log.d("URI-FOUND",p.getUri().toString());    if(p.getUri().toString().equals("content://com.android.externalstorage.documents/tree/primary%3AAndroid")){        this.permUri = p.getUri();    }    else{        startActivityForResult((new Intent("android.intent.action.OPEN_DOCUMENT_TREE")).putExtra("android.provider.extra.INITIAL_URI", (Parcelable)DocumentsContract.buildDocumentUri("com.android.externalstorage.documents", "primary:Android")), 555);    }}

Once we have the Uri, we can perform different operations, e.g.

  • Creating a folder.
  • Creating a file.
  • Writing a file.

This can be a little tricky because you have to work with DocumentsContract, DocumentFile, etc. But don’t worry, I have the code for you here:

Example:

try {    

//CREATE A FOLDER:    
Uri folderUri = DocumentsContract.buildChildDocumentsUriUsingTree(permUri, DocumentsContract.getTreeDocumentId(permUri) + "/data/");    Uri newFolderUri = DocumentsContract.createDocument(requireActivity().getContentResolver(), folderUri, DocumentsContract.Document.MIME_TYPE_DIR, "test2");        

//CREATE A FILE:    
Uri newFolderUri2 = DocumentsContract.buildChildDocumentsUriUsingTree(permUri, DocumentsContract.getTreeDocumentId(permUri) + "/data/test2");    Uri fileUri = DocumentsContract.createDocument(requireActivity().getContentResolver(), newFolderUri2, "text/plain", "test23123");        

//WRITE TEXT TO A FILE    
DocumentFile f2 = DocumentFile.fromSingleUri(requireActivity().getApplicationContext(), fileUri);    OutputStream oS = requireActivity().getContentResolver().openOutputStream(fileUri);    oS.write("TEXT".getBytes(StandardCharsets.UTF_8));    oS.close();} catch (Exception e) {    e.printStackTrace();
}

Thanks for reading till the end. Hope you find this useful.