Support for Storage Access Framework (SAF) URIs

350 views
Skip to first unread message

Ferhat

unread,
Jun 18, 2026, 9:29:22 AMJun 18
to DroidScript

Hello,

I'm having an issue with Storage Access Framework (SAF).

When I select a folder, DroidScript returns a URI such as:

content://com.android.externalstorage.documents/tree/primary:Documents/MyData

I cannot use this URI with app.ListFolder().

I also tried using the equivalent filesystem path:

/storage/emulated/0/Documents/MyData

Even after obtaining write permission and successfully creating files with app.WriteFile(), app.ListFolder() is still unable to list the folder contents.

Is there a way to access and list files from a SAF folder URI, or is this a limitation of app.ListFolder()?

If not, could support for SAF URIs be added to functions like ListFolder(), ReadFile(), and WriteFile()? Android increasingly returns content URIs instead of direct file paths, so it would be very useful.

Thank you.

Alan Hendry

unread,
Jun 18, 2026, 10:51:51 AMJun 18
to DroidScript
HI, 
I think this was brought up in
and is outstanding.
Regards, ah

Ferhat

unread,
Jul 4, 2026, 7:13:56 AMJul 4
to DroidScript

Hello,

Thanks but, I'm currently working on an urgent project. I need to list the files and subfolders inside a folder that the user has granted access to using ListFolder.

I can successfully request and obtain permission to access the folder, but ListFolder does not return its contents.

Is there another way to list the files and folders in a user-selected directory? If this is not currently supported, is there any plan to add this functionality in a future update? If so, is there an estimated release date?

Thank you for your help.


18 Haziran 2026 Perşembe tarihinde saat 17:51:51 UTC+3 itibarıyla hendr...@gmail.com şunları yazdı:

Jumar

unread,
Jul 5, 2026, 6:11:51 AMJul 5
to DroidScript
You can try this:

appFolder = "/Internal/Documents/MyData"
var files = app.ListFolder(appFolder)

Ferhat

unread,
Jul 8, 2026, 9:05:13 AMJul 8
to DroidScript
Hello Jumar,
I tried, but it didn't work.


Example code:

//Called when application is started.
function OnStart()
{
 //Create a layout with objects vertically centered.
 lay = app.CreateLayout( "Linear", "VCenter,FillXY" )
  
 filelist = app.CreateText( "Files: ",1,null )
 lay.AddChild( filelist )
 permbtn = app.CreateButton( "Get Perm" )
 permbtn.SetOnTouch( permbtn_OnTouch )
 lay.AddChild( permbtn )
 //Add layout to app. 
 app.AddLayout( lay )
}

function permbtn_OnTouch()
{
 app.GetPermission( "Internal", OnPermission ); // Get acces
}

function OnPermission( pathURI )
{
  app.WriteFile( pathURI+"/demo.txt", "Hello" ) // Succes
  filelist.SetText( "Files: "+app.ListFolder(pathURI) ) // Error
  if(app.ListFolder(pathURI)=="") app.ShowPopup( "No files were found to list." ) // Error
}

5 Temmuz 2026 Pazar tarihinde saat 13:11:51 UTC+3 itibarıyla Jumar şunları yazdı:

Alan Hendry

unread,
Jul 9, 2026, 4:34:16 AMJul 9
to DroidScript
HI,
Someone posted code (testUriFileFunctions)
that seems to show that some file functions
don't work with uris provided by GetPermissions.
Regards, ah

appFilename  = app.GetAppName() + ".js"
testFilename = "test.js"
myFilename   = "myFile.js"
permUri      = ""

function OnStart()
{
    lay = app.CreateLayout( "linear", "VCenter,FillXY" );
    permBtn = app.AddButton(lay, "GetPermission - choose a foldername without spaces", -1, -1, "Monospace");
    permBtn.SetOnTouch(permBtnOnTouch)

    app.AddLayout( lay );
}

function permBtnOnTouch() {
    permUri = ""
    app.GetPermission("internal", onPermission)
}

function onPermission(uri) {
    if (uri) {
        uri = app.Uri2Path(uri) // ?
        txtUriHeader = app.AddText(lay, "Choosed Uri folder", -1, -1, "Bold")
        txtUri = app.AddText(lay, "", -1, -1, "MultiLine")
        txtListUriFolderHeader = app.AddText(lay, "Files in Uri folder (no indication)", -1, -1, "Bold")
        txtListUriFolderHeader.SetTextColor("red")
        txtListUriFolder = app.AddText(lay, "", -1, -1, "MultiLine")
        copyFileBtn = app.AddButton(lay, "Copy appFile to Uri folder", -1, -1, "Monospace");
        copyFileBtn.SetOnTouch(copyFileBtnOnTouch)
        copyFolderBtn = app.AddButton(lay, "Copy Uri folder to 'copiedFolder' in Uri folder (warning)", -1, -1, "Monospace");
        copyFolderBtn.SetTextColor("red")
        copyFolderBtn.SetOnTouch(copyFolderBtnOnTouch)
        fileExistsBtn = app.AddButton(lay, "Check existence of appFile in Uri folder (always false)", -1, -1, "Monospace");
        fileExistsBtn.SetTextColor("red")
        fileExistsBtn.SetOnTouch(fileExistsBtnOnTouch)
        folderExistsBtn = app.AddButton(lay, "Check existence of Uri folder (always false)", -1, -1, "Monospace");
        folderExistsBtn.SetTextColor("red")
        folderExistsBtn.SetOnTouch(folderExistsBtnOnTouch)
        delFileBtn = app.AddButton(lay, "Delete appFile in Uri folder", -1, -1, "Monospace");
        delFileBtn.SetOnTouch(delFileBtnOnTouch)
        delFolderBtn = app.AddButton(lay, "Delete Uri folder (no indication)", -1, -1, "Monospace");
        delFolderBtn.SetTextColor("red")
        delFolderBtn.SetOnTouch(delFolderBtnOnTouch)
        readFileBtn = app.AddButton(lay, "Read content of appFile in Uri folder", -1, -1, "Monospace");
        readFileBtn.SetOnTouch(readFileBtnOnTouch)
        writeFileBtn = app.AddButton(lay, "Write 'myFile.js' to Uri folder", -1, -1, "Monospace");
        writeFileBtn.SetOnTouch(writeFileBtnOnTouch)
        renFileBtn = app.AddButton(lay, "Rename newly written 'myFile.js' in Uri folder to 'renamedFile.js'", -1, -1, "Monospace");
        renFileBtn.SetOnTouch(renFileBtnOnTouch)
        zipFileBtn = app.AddButton(lay, "Zip newly written 'myFile.js' in Uri folder to 'myFile.js.zip' (warning)", -1, -1, "Monospace");
        zipFileBtn.SetTextColor("red")
        zipFileBtn.SetOnTouch(zipFileBtnOnTouch)
        zipFolderBtn = app.AddButton(lay, "Zip Uri folder to 'zippedFolder' in Uri folder (warning)", -1, -1, "Monospace");
        zipFolderBtn.SetTextColor("red")
        zipFolderBtn.SetOnTouch(zipFolderBtnOnTouch)
       
        permUri = uri
        txtUri.SetText(permUri)
        listUriFolder()
    }
}

function copyFileBtnOnTouch() {
    app.CopyFile(appFilename, permUri + "/" + appFilename)
    listUriFolder()
}

function copyFolderBtnOnTouch() {
    app.CopyFolder(permUri, permUri + "/" + "copiedFolder")
    listUriFolder()
}

function fileExistsBtnOnTouch() {
    app.Alert("File exists: " + app.FileExists(permUri + "/" + appFilename))
}

function folderExistsBtnOnTouch() {
    app.Alert("Folder exists: " + app.FolderExists(permUri))
}

function delFileBtnOnTouch() {
    app.DeleteFile(permUri + "/" + appFilename)
    listUriFolder()
}

function delFolderBtnOnTouch() {
    app.DeleteFolder(permUri)
    listUriFolder()
}

function readFileBtnOnTouch() {
    app.Alert("File read: " + app.ReadFile(permUri + "/" + appFilename))
}

function writeFileBtnOnTouch() {
    app.WriteFile(permUri + "/" + myFilename, "//My file")
    listUriFolder()
}

function renFileBtnOnTouch() {
    app.WriteFile(permUri + "/" + myFilename, "//My file")
    app.RenameFile(permUri + "/" + myFilename, permUri + "/" + "renamedFile.js")
    listUriFolder()
}

function zipFileBtnOnTouch() {
    app.WriteFile(permUri + "/" + myFilename, "//My file")
    app.ZipFile(permUri + "/" + myFilename, permUri + "/" + myFilename + ".zip")
    listUriFolder()
}

function zipFolderBtnOnTouch() {
    app.ZipFolder(permUri, permUri + "/" + "zippedFolder")
    listUriFolder()
}

function listUriFolder() {
    txtListUriFolder.SetText(app.ListFolder(permUri).join("\n"))
}

Ferhat

unread,
Jul 10, 2026, 1:45:37 AMJul 10
to DroidScript
I've used this before, but ListFolder doesn't work. It normally contains two files, but it returns an empty.
DS version: 2.79

SS:
Screenshot_20260709-203131.pngScreenshot_20260710-084308.png

9 Temmuz 2026 Perşembe tarihinde saat 11:34:16 UTC+3 itibarıyla hendr...@gmail.com şunları yazdı:

Alan Hendry

unread,
Jul 12, 2026, 10:45:52 AMJul 12
to DroidScript
HI,
If the first time a user runs an app, and we use GetPermissions,
can we store the uri, and on subsequent runs
just use the stored uri and skip the GetPermissions?
Whether I GetPermissions for ExtSDCard or Internal (or USB)
It seems to ask the user to navigate and choose a folder, 
the start point for the navigation doesn't seem to be predictable.
If we want permission to a specific folder (say documents/newapp)
then we seem to be dependent on the user navigating to documents,
creating folder newapp, navigating to newapp, then giving access.
Would it be possible to GetPermission for the specific folder from the start?
Regards, ah

Alan Hendry

unread,
Jul 13, 2026, 1:03:14 PMJul 13
to DroidScript

HI,
When running under DS ...
GetPermissions for ExtSDcard AND Storage
seem to show up in ListPermissions for Storage (only).
Also Remove Permission for a specific folder uri 
seems to remove permission for all uri's under that folder.
The good news us that the Permission is still there
after DS restarts.
Regards, ah

Alan Hendry

unread,
Jul 13, 2026, 1:05:28 PMJul 13
to DroidScript
var txt1, txt2, txt3, txt4, l1, l2
function OnStart() {
    lay = app.CreateLayout( "linear", "Top,Left,FillXY" )
    txt3 = app.AddText( lay,"Check Permission ExtSDCard " )
    txt4 = app.AddText( lay,"Check Permission Storage" )
    txt1 = app.AddText( lay,"List Permissions ExtSDCard (touch to remove)",-1,-1,"bold" )
    l1 = app.AddList( lay,app.ListPermissions( "ExtSDcard" ) )
    l1.SetOnTouch( function (t,b,ix) {alert(t);app.RemovePermission(t);lc();})
    txt2 = app.AddText( lay,"List Permissions Storage (touch to remove)" ,-1,-1,"bold")
    l2 = app.AddList( lay,app.ListPermissions( "Storage" ) )
    l2.SetOnTouch( function (t,b,ix) {alert(t);app.RemovePermission(t);lc();})
    permBtn = app.AddButton(lay, "GetPermission ExtSDcard - choose a folder without spaces", -1, -1);
    permBtn.SetOnTouch(function() {app.GetPermission("ExtSDcard",lc)})
    permBtn = app.AddButton(lay, "GetPermission Internal - choose a folder without spaces", -1, -1);
    permBtn.SetOnTouch(function() {app.GetPermission("Internal",lc)})
    permBtn = app.AddButton(lay, "GetPermission External - choose a folder without spaces", -1, -1);
    permBtn.SetOnTouch(function() {app.GetPermission("External",lc)})
    app.AddLayout( lay );
    lc()
}
function lc () {
    check = app.ListPermissions( "ExtSDcard" )
    l1.SetList( check.replaceAll(":","^c^" ))
    check = app.ListPermissions( "Storage" )
    l2.SetList( check.replaceAll(":","^c^" ))
    check = app.CheckPermission( "ExtSDcard" )
    txt3.SetText( "Check Permission ExtSDcard ungranted = " + check )
    check = app.CheckPermission( "Storage" )
    txt4.SetText("Check Permission Storage ungranted = " + check )
}

Jumar

unread,
Jul 14, 2026, 8:21:52 AMJul 14
to DroidScript
Hi Ferhat

Try this code:



var appFolder = "/Internal/Documents/MyNewFolder"
var count = 0


//Called when application is started.
function OnStart()
{
    //Create a layout with objects vertically centered.
    lay = app.CreateLayout( "Linear", "VCenter,FillXY" )
     
    filelist = app.CreateText( "Files: ",1,null )
    lay.AddChild( filelist )
   
    writeBtn = app.CreateButton( "Write file" )
    writeBtn.SetOnTouch( CreateFile )
    lay.AddChild( writeBtn )

   
    //Add layout to app.
    app.AddLayout( lay )
}

function CreateFile()
{
    var fileName = "demo_"+count+".txt"
    var fileContent = "This is the "+count+"file"
    WriteFile(fileName, fileContent)
    count++
}

function WriteFile(fileName, fileContent)
{
    if( !app.CheckPermission(appFolder) ) {
        app.GetPermission("internal", path => {
            if (!path) {
                app.ShowPopup("Permission not granted")
            }
            else {
                const folderName = path.split("tree/primary%3A")[1].replace("%2F", "/")
                appFolder = "/Internal/"+folderName+"/MyNewFolder"
                WriteFile(fileName, fileContent)
            }
        })
        return
    }
   
    app.MakeFolder(appFolder)
    const filePath = appFolder+"/"+fileName
    app.WriteFile(filePath, fileContent)
   
    var files = app.ListFolder(appFolder)
    filelist.SetText(files)

Ferhat

unread,
Jul 15, 2026, 5:01:22 AMJul 15
to DroidScript
Hi Jumar,

I tested the code. ListFolder works correctly immediately after the user grants access to a folder. However, after restarting the app, it returns an empty list for the same folder, even though the permission was previously granted. It only lists the folder contents on the initial permission grant.

14 Temmuz 2026 Salı tarihinde saat 15:21:52 UTC+3 itibarıyla Jumar şunları yazdı:

Jumar

unread,
Jul 15, 2026, 6:11:36 AMJul 15
to DroidScript
Make sure to save the appFolder path before closing the app.


var appFolder = "/Internal/Documents/MyNewFolder"
var count = 0

//Called when application is started.
function OnStart()
{
   appFolder = app.LoadText("appFolder", "/Internal/Documents/MyNewFolder")
    app.SaveText("appFolder", appFolder)
   
    var files = app.ListFolder(appFolder)
    filelist.SetText(files)

}

Ferhat

unread,
Jul 17, 2026, 3:39:04 AMJul 17
to DroidScript
Thanks, Jummar.
The code worked. I can now list the contents of the folder. However, shouldn't ListFolder() normally work with a URI? Also, the path we're using is fixed. I think it would be better to let users choose the folder they want to access.

15 Temmuz 2026 Çarşamba tarihinde saat 13:11:36 UTC+3 itibarıyla Jumar şunları yazdı:

Jumar

unread,
Jul 17, 2026, 8:04:36 AMJul 17
to DroidScript
The path is not fixed. Users can choose a folder they want. Try to select other folders as well. It will work.

Alan Hendry

unread,
Jul 18, 2026, 10:54:03 AMJul 18
to DroidScript
HI,
If I use Jumar's code above to get permission to a folder,
then use my code above (different app) to list permissions
then the permissions seem to show up.
(Also every time Jumar's code is restarted, 
the count starts at zero, 
it could be saved and loaded as numeric)
Regards, ah

Ferhat

unread,
Jul 19, 2026, 9:16:30 AMJul 19
to DroidScript
Thank you very much

18 Temmuz 2026 Cumartesi tarihinde saat 17:54:03 UTC+3 itibarıyla hendr...@gmail.com şunları yazdı:

Alan Hendry

unread,
Jul 19, 2026, 12:09:11 PMJul 19
to DroidScript

HI, 
To clarify, this is when running under DS.
Also not sure if I understand Jumar's code -
Variable appFolder is set to a path on the first run, 
which is used in CheckPermission, but the docs show parm should be type.
Also not sure why function Writefile seems to invoke function Writefile
(inside an else inside the arrow function for GetPermission inside an if)
Regards, ah

Ferhat

unread,
Jul 19, 2026, 5:52:07 PMJul 19
to DroidScript
Hi,

I’m not completely sure about the code myself, so I’m trying to understand it through testing.

I have used DroidScript for many years, and this behavior did not happen in older versions. It seems to have started after recent DroidScript updates and the changes in Android/Google Play storage access rules requiring URI-based access.

Before these changes, we could request permission once and then use ListFolder() to access directories directly. The new storage access model appears to have changed this workflow significantly.


19 Temmuz 2026 Pazar tarihinde saat 19:09:11 UTC+3 itibarıyla hendr...@gmail.com şunları yazdı:

Jumar

unread,
Jul 19, 2026, 8:49:15 PMJul 19
to DroidScript
The code I provided is just the same as the Storage Access sample code in Samples section of DS.

Regards

Alan Hendry

unread,
Jul 22, 2026, 4:44:35 AMJul 22
to DroidScript
HI,
In 2.70.1 the Sample (top-right icon) for Storage Access 
is somewhat different.

//Called when application is started.
function OnStart()
{
    //Create a layout with objects vertically centered.
    lay = app.CreateLayout( "linear", "VCenter,FillXY" )

    //Add a spinner control to select storage type.
    spin = app.AddSpinner( lay, "Internal,External,USB", 0.5 );
    spin.SelectItem( "Internal" )

    //Create a test button.
    btnWrite = app.AddButton(  lay, "Write to Storage", 0.5 )
    btnWrite.SetOnTouch( btnWrite_OnTouch )
    btnWrite.SetMargins( 0, 0.05, 0, 0 )

    //Create a button to clear write permissions.
    btnRemove = app.AddButton(  lay, "Remove Permissions", 0.5 )
    btnRemove.SetOnTouch( btnRemove_OnTouch )
    btnRemove.SetMargins( 0, 0.05, 0, 0 )


    //Add layout to app.
    app.AddLayout( lay )
}

//Handle the 'write to storage' button.
function btnWrite_OnTouch()
{
    WriteToStorage()
}

//Handle the 'remove permissions' button.
function btnRemove_OnTouch()
{
    app.RemovePermission( "*" )
}

//Write a file to the chosen storage type.
function WriteToStorage()
{
    //Set internal or external storage folder.
    if( spin.GetText()=="Internal" )
    {
        fldr = "/Internal/Documents/MyData"
        perm = "internal"
    }
    else if( spin.GetText()=="External" )
    {
        fldr = "/External/Documents/MyData"
        perm = "external"
    }
    else
    {
        fldr = "/USB/MyData"
        perm = "usb"
    }

    //Check if we have permission to write to folder.
    if( !app.CheckPermission( fldr ) )
    {
        alert( "Please give permission to access the " + fldr
            + " folder or a parent folder" )
        app.GetPermission( perm, OnPermission )
        return
    }

    //Create folder and write a file.
    app.MakeFolder( fldr )
    app.WriteFile( fldr+"/file.txt",  "Hello" )

    //Read back file to check it worked.
    var s = app.ReadFile( fldr+"/file.txt" )
    if( s=="Hello" ) app.ShowPopup( "written: " + s )
    else app.ShowPopup( "write failed!" )
}

//Handle result of permission request.
function OnPermission( path, uri )
{
    if( !path )
        app.ShowPopup( "Permission not granted!" )
    else
        WriteToStorage()
}
Regards, ah

Ferhat

unread,
Jul 22, 2026, 9:46:24 AMJul 22
to DroidScript
I've temporarily solved the problem with a code like this. Thanks for the support.


var appFolder = "/Internal/Documents/MyNewFolder"
var count = 0

//Called when application is started.
function OnStart()
{
   appFolder = app.LoadText("appFolder", "/Internal/Documents/MyNewFolder")

    //Create a layout with objects vertically centered.
    lay = app.CreateLayout( "Linear", "VCenter,FillXY" )
     
    filelist = app.CreateText( "Files: ",1,null )
    lay.AddChild( filelist )
     
    writeBtn = app.CreateButton( "Write file" )
    writeBtn.SetOnTouch( CreateFile )
    lay.AddChild( writeBtn )

     
    //Add layout to app.
    app.AddLayout( lay )
}

function CreateFile()
{
    var fileName = "demo_"+count+".txt"
    var fileContent = "This is the "+count+"file"
    WriteFile(fileName, fileContent)
    count++
}

function WriteFile(fileName, fileContent)
{
    if( !app.CheckPermission(appFolder) ) {
        app.GetPermission("internal", path => {
            if (!path) {
                app.ShowPopup("Permission not granted")
            }
            else {
                const folderName = path.split("tree/primary%3A")[1].replace("%2F", "/")
                appFolder = "/Internal/"+folderName+"/MyNewFolder"
                WriteFile(fileName, fileContent)
            }
        })
        return
    }
   
    app.MakeFolder(appFolder)
    const filePath = appFolder+"/"+fileName
    app.WriteFile(filePath, fileContent)

    app.SaveText("appFolder", appFolder)
   
    var files = app.ListFolder(appFolder)
    filelist.SetText(files)
}


22 Temmuz 2026 Çarşamba tarihinde saat 11:44:35 UTC+3 itibarıyla hendr...@gmail.com şunları yazdı:
Reply all
Reply to author
Forward
0 new messages