Android Question How to access the whole of external SD card?

Inman

Well-Known Member
Licensed User
I am currently working on an app that is for Android 5.0 and above. From a service I need to copy some files to external SD card (not internal SD card returned by File.DirRootExternal). I specifically need access to the root of the external SD card as the files are to be copied to a folder that is NOT created by my app.

I read that this can be done using intent ACTION_OPEN_DOCUMENT_TREE. This stackoverflow post talks about it.

https://stackoverflow.com/questions/26744842/how-to-use-the-new-sd-card-access-api-presented-for-android-5-0-lollipop

What I am trying to do is display a folder picker that shows both internal and external sd cards, let the user select a folder and then copy some files to it, irrespective of whether the user-selected folder is internal or external.

How to do this in B4A?
 

Inman

Well-Known Member
Licensed User
I did. I first tried runtimepermissions. Got permission for android.permission.WRITE_EXTERNAL_STORAGE but still couldn't write to external SD card.

I then tried the attached project in the link you shared. It displays a folder picker and I am able to see and select folders on external SD card. But I still can't write to it.

It must be possible as file explorer apps like ES File Explorer and Solid Explorer manage to have complete write access on external SD.
 

Inman

Well-Known Member
Licensed User
Thanks Don. I learnt a lot from your example, especially how Storage works. But I am still facing the issue. I guess with your Storage library, the issue can be summarised to a single line.

How can I perform File.WriteString(path,"testfile.txt","abcdef") if storage.isExternalStorageRemovable(path)=True?
 

Mahares

Well Known Member
Licensed User
How can I perform File.WriteString(path,"testfile.txt","abcdef") if storage.isExternalStorageRemovable(path)=True?
I hope this is what you are trying to achieve. Try something like this with runtime permission and phone lib only:
B4X:
Sub Globals
    Dim rp As RuntimePermissions
    Private ph As Phone
End Sub

Sub Activity_Create(FirstTime As Boolean)   
    Log($"SDK version is: ${ph.SdkVersion}"$)  
    Log($"-----------"$)
   
    Dim n As Int
    Dim path As String
    For Each s As String In rp.GetAllSafeDirsExternal("")  'returns a string array
        Log(s)
        If n=1 Then path=s   'removable SD could also be a different number
        n=n+1
    Next
    File.WriteString(path,"testfile.txt","abcdef")  'file saved to removable SD card in folder Android/Data/b4a.example/files folder
    Log($"File saved to ${path}"$)  'File saved to /storage/6130-3538/Android/data/b4a.example/files
End Sub
 

ac9ts

Active Member
Licensed User
I am also interested in this. I can write to the application directory (Android/data) on the SD card. Like Inman, I would like to write to a root directory of the SD so the contents don't get erased when the application is uninstalled.
 

Inman

Well-Known Member
Licensed User
I hope this is what you are trying to achieve. Try something like this with runtime permission and phone lib only:
Thank you Mahares, but according to your code comment, the file is saved to /storage/6130-3538/Android/data/b4a.example/files. I want to save to /storage/6130-3538/ or maybe a subfolder like /storage/6130-3538/Screenshots/

It is definitely possible. The stackoverflow links I shared explains how to do it in Java.

Check this Java code
PHP:
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
    if (resultCode != RESULT_OK)
        return;
    else {
        Uri treeUri = resultData.getData();
        DocumentFile pickedDir = DocumentFile.fromTreeUri(this, treeUri);
        grantUriPermission(getPackageName(), treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        copyFile(sdCard.toString(), "/File.txt", path + "/new", pickedDir);
    }
}
I think the key is grantUriPermission which grants the app the permission to write to the selected folder. It looks like this permission is different from Write To External Storage permission that we normally use.
 

Erel

Administrator
Staff member
Licensed User
Try this code:
B4X:
Sub Activity_Click
   Dim i As Intent
   i.Initialize("android.intent.action.OPEN_DOCUMENT_TREE", "")
   StartActivityForResult(i)
End Sub

Sub ion_Event (MethodName As String, Args() As Object) As Object
   Return Null
End Sub

Sub StartActivityForResult(i As Intent)
   Dim jo As JavaObject = GetBA
   ion = jo.CreateEvent("anywheresoftware.b4a.IOnActivityResult", "ion", Null)
   jo.RunMethod("startActivityForResult", Array As Object(ion, i))
End Sub

Sub GetBA As Object
   Dim jo As JavaObject
   Dim cls As String = Me
   cls = cls.SubString("class ".Length)
   jo.InitializeStatic(cls)
   Return jo.GetField("processBA")
End Sub
Does it let you to choose the external sd card?
 

Inman

Well-Known Member
Licensed User
Yes it does. The code is similar to the sample project attached in this post. In both cases I can choose the external sd card. Problem is when I tried to write a file using the attached project posted by chrjak, I get "Permission Denied" error.
 

Erel

Administrator
Staff member
Licensed User
I'm unable to test it here as I don't have a device with sd card near me.

What is the output of this code:
B4X:
Sub ion_Event (MethodName As String, Args() As Object) As Object
   If Args(0) = -1 Then 'resultCode = RESULT_OK
       Dim i As Intent = Args(1)
       Dim jo As JavaObject = i
       Dim treeUri As JavaObject = jo.RunMethod("getData", Null)
       Dim ctxt As JavaObject
       ctxt.InitializeContext
       Dim DocumentFileStatic As JavaObject
       Dim pickedDir As JavaObject = DocumentFileStatic.InitializeStatic("android.support.v4.provider.DocumentFile").RunMethod("fromTreeUri", Array(ctxt, treeUri))
       Log(pickedDir)
   End If
   Return Null
End Sub
It is based on this answer: https://stackoverflow.com/questions/26744842/how-to-use-the-new-sd-card-access-api-presented-for-android-5-0-lollipop
 

Inman

Well-Known Member
Licensed User
Output is (TreeDocumentFile) android.support.v4.provider.TreeDocumentFile@c5570b

I also don't have a device with SD card. I am testing on an Android 7.0 emulator with external SD card partition. Hope that wouldn't change the results.
 

Erel

Administrator
Staff member
Licensed User
Try this:
B4X:
Sub ion_Event (MethodName As String, Args() As Object) As Object
   If Args(0) = -1 Then 'resultCode = RESULT_OK
       Dim i As Intent = Args(1)
       Dim jo As JavaObject = i
       Dim treeUri As JavaObject = jo.RunMethod("getData", Null)
       Dim ctxt As JavaObject
       ctxt.InitializeContext
       Dim DocumentFileStatic As JavaObject
       Dim pickedDir As JavaObject = DocumentFileStatic.InitializeStatic("android.support.v4.provider.DocumentFile").RunMethod("fromTreeUri", Array(ctxt, treeUri))
       Dim files() As Object = pickedDir.RunMethod("listFiles", Null)
       For Each f As JavaObject In files
           Log(f.RunMethod("getName", Null))
       Next
       Dim newFile As JavaObject = pickedDir.RunMethod("createFile", Array("text/plain", "My Novel"))
       Dim out As OutputStream = ctxt.RunMethodJO("getContentResolver", Null).RunMethod("openOutputStream", Array(newFile.RunMethod("getUri", Null)))
       Dim tw As TextWriter
       tw.Initialize(out)
       tw.Write("this is a test")
       tw.Close
   End If
   Return Null
End Sub
 

Inman

Well-Known Member
Licensed User
Wow you did it! It successfully created a file in the root of external SD card. I tried again but this time selecting a subfolder in the root of external SD card and it worked then as well.

This is the working solution. Now I want to perform a File.Copy() from internal memory to any folder (or root) that user selects on external SD card. How can I change the above code to do that?
 

Mahares

Well Known Member
Licensed User
I also don't have a device with SD card. I am testing on an Android 7.0 emulator
I have a device with Android 7 which has a removable SD card. I or some one else more capable can test it for you if you zip and export a project based on Erel's code to you.
 

Inman

Well-Known Member
Licensed User
Thanks Mahares. I hope we can soon adapt the code in such a way that:
  1. User selects the folder once
  2. Perform File.Copy() to the above folder any number of times without asking the user to select folder again
 

Erel

Administrator
Staff member
Licensed User
Once you have an output stream you can copy a file with File.Copy2. Don't forget to close the outputstream when done.
 

Inman

Well-Known Member
Licensed User
Once you have an output stream you can copy a file with File.Copy2. Don't forget to close the outputstream when done.
Does it need to be done every time via ion_Event subroutine? I was hoping I could display that folder picker using android.intent.action.OPEN_DOCUMENT_TREE just once and then save that path to a file. Then using a service in the background I want to perform File.Copy(or File.Copy2) any number of times without user involvement.

Will this be possible?
 

Erel

Administrator
Staff member
Licensed User
Make pickedTree a process global variable. You can then access it from wherever you want.
 
Top