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

Inman

Well-Known Member
Licensed User
Longtime 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...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
Longtime User
Make pickedTree a process global variable. You can then access it from wherever you want.

Ok. What about permission? Say I make the user select external SD card folder today with the above code. Will I be able to write into that folder, say 2 weeks from now, using a background service, without any user interaction?
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Updated 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
   If Args(0) = -1 Then 'resultCode = RESULT_OK
       Dim i As Intent = Args(1)
       Dim jo As JavaObject = i
       Dim treeUri As Uri = jo.RunMethod("getData", Null)
       Dim ctxt As JavaObject
       ctxt.InitializeContext
       Dim takeFlags As Int = Bit.And(i.Flags, 3)
       ctxt.RunMethodJO("getContentResolver", Null).RunMethod("takePersistableUriPermission", Array(treeUri, takeFlags))
       Dim uri As String = treeUri 'ignore
       'uri should be a process global variable and you can save it
   End If
   Return Null
End Sub

Sub GetPickedDir (uri As String) As JavaObject
   Dim ctxt As JavaObject
   ctxt.InitializeContext
   Dim DocumentFileStatic As JavaObject
   Dim treeUri As Uri
   treeUri.Parse(uri)
   Dim pickedDir As JavaObject = DocumentFileStatic.InitializeStatic("android.support.v4.provider.DocumentFile").RunMethod("fromTreeUri", Array(ctxt, treeUri))
   Return pickedDir
   'usage example of pickedDir
'   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 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

You can save the uri string and use it later without requesting a new permission.
Depends on ContentResolver and JavaObject libraries.
 
Upvote 0

Inman

Well-Known Member
Licensed User
Longtime User
Thank you so much, Erel. The permission is retained correctly, even after restarting emulator. Couple more requests.

1. Just like how you opened an outputstream, how can I open an inputstream for a treeuri like in the above code so that I can use File.Copy2?

2. How can I get the real path of a uri (for display purpose)? Currently when I print uri, I get (HierarchicalUri) content://com.android.externalstorage.documents/tree/17FC-3214%3Atff

I then tried this
B4X:
jo=treeUri
Log(jo.RunMethod("getPath",Null))

But it printed /tree/17FC-3214:tff. How can I get a path like /storage/17FC-3214/tff? I saw this stackoverflow post. Could you please take a look?

https://stackoverflow.com/questions...th-from-uri-from-mediastore/10564727#10564727
B4X:
private String getRealPathFromURI(Uri contentUri) 
{
    String[] proj = { MediaStore.Images.Media.DATA };
    CursorLoader loader = new CursorLoader(mContext, contentUri, proj, null, null, null);
    Cursor cursor = loader.loadInBackground();
    int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
    cursor.moveToFirst();
    String result = cursor.getString(column_index);
    cursor.close();
    return result;
}
 
Upvote 0

DonManfred

Expert
Licensed User
Longtime User
Upvote 0

Inman

Well-Known Member
Licensed User
Longtime User
https://www.b4x.com/android/forum/threads/using-photos-from-the-phone.37444/#post-221367

Note that there are cases where IT DOES NOT WORK! DONT RELY ON THE REAL PATH!!!!

Thank you Don. I won't be using real path for any operation; just to show user the path he selected.

Btw that code is throwing error on line Cursor1 = cr.Query(Uri1, Proj, "", Null, "")
B4X:
java.lang.UnsupportedOperationException: Unsupported Uri content://com.android.externalstorage.documents/tree/17FC-3214%3Atff

Google must have changed something is the 4 years that code was posted
 
Upvote 0

Inman

Well-Known Member
Licensed User
Longtime User
Just replace "openOutputStream" with "openInputStream".

What about the newFile javaobject that is passed with openOutputStream?
B4X:
Dim newFile As JavaObject = pickedDir.RunMethod("createFile", Array("text/plain", "My Novel"))
What should I replace "createFile" with?
 
Upvote 0

Inman

Well-Known Member
Licensed User
Longtime User
Thanks for the all the help, Erel. I am posting the code I came up with as it may help someone someday. Please check this question I posted which is connected to this discussion.
B4X:
Sub StreamFileCopier(dirsource As String,filesource As String,dirdest As String,filedest As String)

    Dim inp As InputStream,out As OutputStream
    Dim suri,duri As String
    
    Dim sctxt,dctxt,spickedDir,dpickedDir As JavaObject
        
    suri=dirsource
    spickedDir=GetPickedDir(suri)
    sctxt.InitializeContext
    
    duri=dirdest
    dpickedDir=GetPickedDir(duri)
    dctxt.InitializeContext
    
    Dim oldFile As JavaObject = spickedDir.RunMethod("findFile", Array(filesource))
    inp = sctxt.RunMethodJO("getContentResolver", Null).RunMethod("openInputStream", Array(oldFile.RunMethod("getUri", Null)))
    
    Dim newFile As JavaObject = dpickedDir.RunMethod("createFile", Array("text/plain", filedest))
    out = dctxt.RunMethodJO("getContentResolver", Null).RunMethod("openOutputStream", Array(newFile.RunMethod("getUri", Null)))
    
    File.Copy2(inp,out)
    out.Close
    
End Sub
 
Upvote 0
Top