Read and write on android 10

1,386 views
Skip to first unread message

Andries de Man

unread,
Dec 28, 2020, 8:25:37 PM12/28/20
to DroidScript
Halo.I just wanna find out,I have installed the newest droidscript on top of version 1.80.so far no problems but what I wanna know is will app.writefile,app.readfile etc. work on android 10?I mean can we still do this?  app.Writefile( "/sdcard/test.js", test);I hear there is problem with android 10 scoped storage.
Thanks in advance

Caiota

unread,
Dec 28, 2020, 9:31:01 PM12/28/20
to DroidScript
the same for me, I can't write or read anything outside the Android folder to the app, but within the droidscript I can, I don't know what to do besides downgrading and using version 1.80 forever

Dave

unread,
Dec 29, 2020, 6:10:45 AM12/29/20
to DroidScript
Sticking with 1.80 won't really help you if you want to release apps on Google Play, because any apps you create must be API 29 and will have scoped storage on Android 10+

You may need to re-think your app design for Android 10 devices.

Dave

unread,
Dec 29, 2020, 6:14:13 AM12/29/20
to DroidScript
FYI: When you call  app.Writefile( "/sdcard/test.js", test )  it will be written to the /storage/Android/data/com.yourpackage.yourapp/files/test.js folder on Android 10+

All /sdcard/* paths are mapped by DS automatically to that folder on Android 10+

Andries de Man

unread,
Dec 29, 2020, 7:00:30 AM12/29/20
to DroidScript
Ok so if I want to write to the main internal folder like it work on older devices how must I write it Dave?can you please show me the path I must enter to write to main folder?
Thanks for your reply

Dave

unread,
Dec 29, 2020, 7:05:21 AM12/29/20
to DroidScript
You can't on Android 10.. that's the point.

Dave

unread,
Dec 29, 2020, 7:09:22 AM12/29/20
to DroidScript
Not without asking for special permissions from the user.

Andries de Man

unread,
Dec 29, 2020, 7:17:23 AM12/29/20
to DroidScript
Ok can I detect if there's a memory card and then let user know that I gonna write to memory card?

Steve Garman

unread,
Dec 29, 2020, 7:31:33 AM12/29/20
to DroidScript
I recommend that you write to the folder pointed at by
  sPath = app.GetPrivateFolder( "","external" )
You can write to it and the user can read from it with no special permission

Andries de Man

unread,
Dec 29, 2020, 7:51:16 AM12/29/20
to DroidScript
Ok thank you Steve.that seems to be the only choice I have.thank you everyone

Alan Hendry

unread,
Dec 29, 2020, 11:14:21 AM12/29/20
to DroidScript
Hi,
If I understand correctly, apps will only be able to read/write/save/delete/rename/etc to the folder returned by GetPrivateFolder (except for system apps).
I note that in V2 we can ask for external in GetPrivateFolder (presumably default is internal).
What will be returned by GetExternalFolder, GetInternalFolder, GetSpecialFolder?
What will GetPath return when running from the DS app, versus running as an APK?  
Would it make sense to have an internal/external option for GetSpeclalFolder?
(mapping to the appropiate subfolder under the folder returned by GetPrivateFolder).
I presume GetMediaFile will generate a path-file that is under GetPrivateFolder.
Again how about an option for internal/external in GetMediaFile.
Regards, ah

Dave

unread,
Dec 29, 2020, 11:39:14 AM12/29/20
to DroidScript
@Alan: Some of these functions may return paths to the main storage area which will not be accessible in scoped storage mode, but if you then try to do operations that include those paths, such as app.CopyFile() then DS will map the paths to internal ones before trying to do the operation anyway.  DS basically tries to make hide the problem so that transitioning from older versions of Android is as seamless as possible and so your old apps still work despite having lots of /sdcard/* paths in them.

 @Anyone: It would be useful if someone wrote a quick test app to check all these paths actually.   

@bsishange: If you tell us what your particular scenario is, we might be able to offer an alternative solution.

Dave

unread,
Dec 29, 2020, 11:45:40 AM12/29/20
to DroidScript
P.S.  I would recommend using the new paths '/Storage' and '/External' going forward as they map to the appropriate place whether storage is scoped or not.

Eduardo Paredes

unread,
Dec 29, 2020, 12:24:00 PM12/29/20
to DroidScript
@Dave

I have an APP, with more than 500 users, that uses a SQLITE database, and the FTP plugin

All the APP data is stored in "/ sdcard / myapp /"

The APP works 100% correctly from DroidScript, in all the Android versions that I have tried, including 10, the data is stored in the established path and everything works fine.



The APK it works fine on Android 9 or less, however on Android 10 I have the following issues.
1. Users can no longer access their data in “/ sdcard / myapp /”
2. app. OpenDatabase (localpath + ”mydata.sql”) does not create the database, gives an access denied error.
3. ftp.DownloadFile (remote + master_file, localtmp + master_file, "ISO-8859-1"); it also gives error.

I have tried to change the paths in Android 10, and I manage to create the folder structure I need in com.xxxx.myapp / files ... but I cannot create the database. And neither download files to this route with FTP.

/Storage also doesn't work with app.opendatabase ()

Please, can you help me.

ps: Why does DroidScrip work in one way and the APK in another? This should not be like that?

Dave

unread,
Dec 29, 2020, 2:17:18 PM12/29/20
to DroidScript
As Steve says you can use the  app.GetPrivateFolder( "","external" )  method to get a valid writable path for any version of Android.

I suggest you use that method to get paths to give to the OpenDatabase() and ftp.DownloadFile() methods.  

Unfortunately those methods are unaware of the new 'Scoped Storage' restrictions in Android 10, so you have to pass the fully resolved path to them.

The inconsistency between running inside DS and a released APK on Android 10+ is something that we cannot do anything about... its Google's changes that have caused this headache!

Steve Garman

unread,
Dec 29, 2020, 2:19:48 PM12/29/20
to DroidScript
The code below creates a database whether run in DS or an apk, including on Android 10

//Called when application is started.  
dbFile=app.GetPrivateFolder( "dbase","external" )+
  "/myData"
app.Alert(dbFile,"Stored at")
function OnStart()  
{  
    //Create a layout with objects vertically centered.  
    lay = app.CreateLayout( "linear", "VCenter,FillXY" )  
      
    //Create an 'Add' button.  
    btnAdd = app.CreateButton( "Add to Database", 0.6, 0.1 )  
    btnAdd.SetOnTouch( btnAdd_OnTouch )  
    lay.AddChild( btnAdd )  
      
    //Create a 'Remove' button.  
    btnRemove = app.CreateButton( "Remove from Database", 0.6, 0.1 )  
    btnRemove.SetOnTouch( btnRemove_OnTouch )  
    lay.AddChild( btnRemove )  
      
    //Create a 'Delete' button.  
    btnDelete = app.CreateButton( "Delete Database", 0.6, 0.1 )  
    btnDelete.SetOnTouch( btnDelete_OnTouch )  
    lay.AddChild( btnDelete )  
      
    //Create text box to show results.  
    txt = app.CreateText( "", 0.9, 0.4, "multiline" )  
    txt.SetMargins( 0,0.1,0,0 )  
    txt.SetBackColor( "#ff222222" )  
    txt.SetTextSize( 18 )  
    lay.AddChild( txt )  
      
    //Add layout to app.      
    app.AddLayout( lay )  
      
    //Create or open a database in publicly accessible storage
    db = app.OpenDatabase( dbFile )  
      
    //Create a table (if it does not exist already).  
    db.ExecuteSql( "CREATE TABLE IF NOT EXISTS test_table " +  
        "(id integer primary key, data text, data_num integer)" )  

    //Get all the table rows.      
    DisplayAllRows() 
}  

//Called when user touches our 'Add' button.  
function btnAdd_OnTouch()  
{  
    //Add some data (with error handler).  
    db.ExecuteSql( "INSERT INTO test_table (data, data_num)" +   
        " VALUES (?,?)", ["test", 100], null, OnError )  

    //Get all the table rows.      
    DisplayAllRows()  
}  

//Called when user touches our 'Remove' button.  
function btnRemove_OnTouch()  
{      
    //Remove data.  
    db.ExecuteSql( "DELETE FROM test_table WHERE id > 3" )  

    //Get all the table rows.      
    DisplayAllRows()  
}  

//Called when user touches our 'Delete' button.  
function btnDelete_OnTouch()  
{      
   //Delete this database.  
   db.Delete()  

   //Get all the table rows.  
   DisplayAllRows() 
}  

//function to display all records 
function DisplayAllRows() 
    txt.SetText("")  
      
    //Get all the table rows.  
    db.ExecuteSql( "select * from test_table;", [], OnResult ) 

//Callback to show query results in debug.  
function OnResult( results )   
{  
    var s = "";  
    var len = results.rows.length;  
    for(var i = 0; i < len; i++ )   
    {  
        var item = results.rows.item(i)  
        s += item.id + ", " + item.data + ", " + item.data_num + "\n";   
    }  
    txt.SetText( s )  
}  

//Callback to show errors.  
function OnError( msg )   
{  
    app.Alert( "Error: " + msg )  
    console.log( "Error: " + msg )  
}  


Dave

unread,
Dec 29, 2020, 2:29:52 PM12/29/20
to DroidScript
WARNING: If you are developing on Android10, your projects are at risk of being deleted if you uninstall DroidScript!

With a fresh install of DroidScript on Android10+.  Your projects folder may be deleted when the app is uninstalled due to the new 'Scoped Storage' rules introduced in Android 10.

To protect against this, make sure you either send yourself SPKs via email regularly and/or make a backup of the /storage/Android/data/com.smartphoneremote.androidscriptfree folder

Andries de Man

unread,
Dec 29, 2020, 4:47:52 PM12/29/20
to DroidScript
@Dave if I want to make a status saver for WhatsApp,and I want the statuses what the user saves to be copied to the public folder "/sdcard/Pictures/" ,is this doable for android 10?

Caiota

unread,
Dec 29, 2020, 5:43:27 PM12/29/20
to DroidScript
ty so much guys, 

 var sPath = app.GetPrivateFolder( "","external" )
  var pos = sPath.indexOf("/Android")
  sPath=sPath.slice(0,pos) + "/Android/data/com.rsg.bgshe/hebgs/.file.js"
  app.LoadScript(sPath,function(){ })

works for me and now my app its working, so i need check some possible bugs now, but thanks 

Dave

unread,
Dec 29, 2020, 6:19:02 PM12/29/20
to DroidScript
@Andries

The trouble with trying to backup WhatsApp status is that the files live here:-
Internal storage > WhatsApp > Media > .Statuses  

But on Android10 that folder will probably move to the Android/data/* folder an not be accessible to your app anymore.  If you don't need to publish your app on the Playstore then you could use a build.json file to target Android API 28 instead of API 29 and you should still have access to that folder (no scoped storage restrictions) assuming WhatsApp don't move it to private folders on Android 10.

This code to grab all received WhatsApp images might also be useful for you (it probably won't grab status images but it should also work on Android10)

//Called when application is started.
function OnStart()
{
//Create a layout with objects vertically centered.
lay = app.CreateLayout( "linear", "VCenter,FillXY" )
//Add a button.
btn = app.AddButton( lay, "Get all Images", 0.4, 0.1 )
btn.SetOnTouch( btn_OnTouch )
//Add an image 20% of screen width.
img = app.AddImage( lay, "/Sys/Img/Hello.png", 0.6 )

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

//Called when user touches our button.
function btn_OnTouch()
    //Query for all image info.
//(Look here for column names: 
    var uri = "content://media/external/images/media";
    var columns = "_data"
    var select = "_data LIKE ?"
    var params = "%WhatsApp%"
    var rows = app.QueryContent( uri, columns, select, params )
    
    //Show result (JSON.stringify as useful way of converting JS objects to text)
    alert( JSON.stringify(rows) )
    
    //Show the image.
    img.SetImage( app.Path2Uri(rows[0]._data) )
}




Andries de Man

unread,
Dec 29, 2020, 9:37:26 PM12/29/20
to DroidScript
Thanks Dave.I just have one last question please.if I write a file like this "/sdcard/file.txt" on android 10 and I want to read the file by above sdcard path will the file be read?sorry for all the questions I don't have a android 10 to test it myself

Steve Garman

unread,
Dec 30, 2020, 2:39:24 AM12/30/20
to DroidScript
Dave's code that starts from 
 var uri = "content://media/external/images/media"
should work consistently on older versions and version 10+ because it does not rely on a specific path

Eduardo Paredes

unread,
Dec 30, 2020, 5:29:11 AM12/30/20
to DroidScript
Yes, my app now works fine on Android 10.

Thank you very much Steve, thank you very much Dave.

And happy new year, everyone.

E. Paredes
 

Alan Hendry

unread,
Dec 30, 2020, 7:19:35 AM12/30/20
to DroidScript
Hi,
There are several examples/samples that use areas outside the PrivateFolder.
Sample Video Player, example Extract assets.
I presume that DS will map them to a folder/file under PrivateFolder.
Should I open a separate thread to request an internal/external option in GetSpecialFolder and GetMediaFile?  
Regards, ah

Alan Hendry

unread,
Dec 30, 2020, 7:49:07 AM12/30/20
to DroidScript
Hi,
Dave said "Not without asking for special permissions from the user."
If I wanted an app to read (say) photos taken by camera or downloaded etc,
how would I go about getting the users permission?
I suspect that if I try to access the Downloads folder then DS will map it to Downloads under PrivateFolder. 
The alternative would be to ask the user to use File Manager to copy 
from Downloads or DCIM/Camera to my app folder (not very user friendly).
Regards, ah

Steve Garman

unread,
Dec 30, 2020, 8:03:21 AM12/30/20
to DroidScript
There is no mechanism in DS 2.02 to request permission
It would probably need something akin.to
    var path = app.GetPermission( "extsdcard", OnPermission );
but it is hard to visualise a simple interface to explain to the user what would be required of them

With photos you may be able to find a mechanism to copy them using content: URIs though I haven tried that yet


Steve Garman

unread,
Dec 31, 2020, 5:29:51 AM12/31/20
to DroidScript
/* 
This code copies any screenshot I choose from
its original location to the publicly accessible
path for this app
*/
var img, lst, rows, 
toPath=app.GetPrivateFolder( "Screenshots","external" );
//Called when application is started.
function OnStart()
{
   //Create a layout with objects vertically centered.
   var lay = app.CreateLayout("linear", "VCenter,FillXY")

   //Add an image 20% of screen width.
   img = app.AddImage(lay, "/Sys/Img/Hello.png", 0.6)

   lst = app.AddList(lay, "", 1)
   lst.RemoveItemByIndex(0)
   lst.Gone();
   lst.SetOnTouch(lst_OnTouch);

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

   //Query for screenshots info.
   //(Look here for column names: 
   var uri = "content://media/external/images/media";
   var columns = "_data"
   var select = "_data LIKE ?"
   var params = "%Screenshots%"
   rows = app.QueryContent(uri, columns, select, params)

   //Show file names
   var s = [ ], len = rows.length
   for(var i = 0; i < len; i++) { s.push(rows[i]._data) }
   s = s.join(",")
   lst.SetList(s);
   lst.Show()
}

function lst_OnTouch(title, body, icon, index)
{
   var name = title.split("/").pop()
   var too = toPath +"/"+name 
   var frm = app.Path2Uri(rows[index]._data)
   
   app.CopyFile( frm, too  )
   if(app.FileExists( too ))
   {
     app.ShowPopup( "copied to "+too )
     img.SetImage( too );

Dave

unread,
Dec 31, 2020, 12:17:25 PM12/31/20
to DroidScript
@ bsishange:   Yes writing and then reading from  /sdcard/file.txt  will work on Android 10.  The file will just end up in the Android/Data/com.smartphoneremote.androidscriptfree/files folder.  

Dave

unread,
Dec 31, 2020, 12:36:42 PM12/31/20
to DroidScript
In Android 10 you should still be able to access the photos and downloads etc via the Android file chooser intents or by using content queries.  If a user picks a file using the built-in Android file dialogs, then it will return a content URI which can be used to access the chosen file using app.ReadFile() or by img.SetImage() as they now support content URIs. (then look like content:/bla/bla...)

These two snippets of code show how to use content queries (query the built-in Android media database) to display images and videos found on the device. (This should all work on Android 10/11 too)

  //Query for all image info.
//(Look here for column names: 
    var uri = "content://media/external/images/media";
    var columns = "_data"
    var rows = app.QueryContent( uri, columns )
    
    //Show result (JSON.stringify as useful way of converting JS objects to text)
    alert( JSON.stringify(rows) )
    
    //Show the image.
    img.SetImage( app.Path2Uri(rows[0]._data) )


 //Query for all image info.
    //var uri = "content://media/external/images/media";
    var uri = "content://media/external/video/media";
    var columns = "_data"
    var rows = app.QueryContent( uri, columns )
    
    //Show result (JSON.stringify as useful way of converting JS objects to text)
    alert( JSON.stringify(rows) )
    
    //Show the first video.
    alert( app.Path2Uri(rows[0]._data) )
    player.SetFile( app.Path2Uri(rows[0]._data) )
    player.Play()




Dave

unread,
Dec 31, 2020, 12:40:17 PM12/31/20
to DroidScript
Just to clarify something the app.ChooseFile() and app.ChooseImage() methods both use intents and return the content URI as the second parameter to the callback in V2

Steve Garman

unread,
Dec 31, 2020, 2:41:24 PM12/31/20
to DroidScript
@Dave
As far ss I can tell your description of the callback parameters of from app.ChooseFile() and app.ChooseImage() is s little misleading

It looks to me like the second parameter is always a filename

In a Scoped Storage environment (Android 10+ fresh install) the first parameter is a content: uri

In a traditional environment, the first parameter is a full path to the file

Steve Garman

unread,
Dec 31, 2020, 3:14:19 PM12/31/20
to DroidScript
// So this works for me in all environments I have tried
// choose any file snd copy to shareable storage

_AddPermissions("Storage" )
function OnStart()
{
   toPath=app.GetPrivateFolder( "myCopies","external" )
   lay = app.CreateLayout("linear", "VCenter,FillXY")

   btn = app.AddButton(lay, "Choose")
   btn.SetOnTouch(btn_OnTouch);

   app.AddLayout(lay);
}

function btn_OnTouch()
{
   app.ChooseFile( "Open","*/*",OnChoose );
}

function OnChoose( path, name)
{
var dest = toPath+"/"+name
app.CopyFile(  path, dest  );
var ok=(app.FileExists( dest  )?"success":"failure")
app.Alert( dest, ok )
 
}

Caiota

unread,
Dec 31, 2020, 4:43:15 PM12/31/20
to DroidScript
estou com um problema para escrever e ler arquivos. app.FileExists retorna verdadeiro, mas não consigo ler nem escrever nada no armazenamento 

Andries de Man

unread,
Dec 31, 2020, 10:43:57 PM12/31/20
to DroidScript
@Dave that is no problem for me.usually I make files what I don't want the user to open.so its not really a problem.thank you Dave!

Steve Garman

unread,
Jan 2, 2021, 3:05:11 AM1/2/21
to DroidScript
/*
  Try to see if scoped storage is in use SJGarman
*/
function OnStart()
{
   LF = "\n\n"
   var package = "/Android/data/" + app.GetPackageName() + "/"
   if(!app.IsAPK())
   {
      app.LoadPlugin("ApkBuilder");
      BUILDER = (new ApkBuilder().GetVersion()).toFixed(2)
   }
   else
   {
      app.WriteFile("/Storage/existTest.txt", "ok")
      BUILDER = app.GetDSVersion() + " apk"
   }
   lay = app.CreateLayout("linear", "VCenter,FillXY")

   if(app.FolderExists("/Storage"))
   {
      temp = (app.ListFolder("/Storage", "", 1, "fullPath")[0])
      if(temp && temp.length > 0)
      {
         temp = temp.split("/")
         temp.pop()
         STORAGE = temp.join("/")
      }
      else STORAGE = "Empty"
   }
   else STORAGE = "Empty"

   SAVETO = STORAGE.includes(package) ?
      "Scoped Storage" : "Traditional Storage"

   VERSION = app.GetDSVersion()

   msg = "Run in app: " + app.GetName() + LF +
      "Storage: " + STORAGE + LF +
      "Type: " + SAVETO + LF +
      "DSVersion: " + VERSION + LF +
      "Builder: " + BUILDER + LF

   yn = app.CreateYesNoDialog(msg)
   yn.SetButtonText("Copy to clipboard", "Ignore")
   yn.SetOnTouch(yn_OnTouch);
   yn.Show()
}

function yn_OnTouch(result)
{
   if(result == "Yes") app.SetClipboardText(msg)
   app.Exit()

Alan Hendry

unread,
Mar 8, 2021, 10:22:43 AM3/8/21
to DroidScript
HI,
RE: the post of  31 Dec 2020, 17:36:42

Not sure what is meant by "Android file chooser intents" and "the built-in Android file dialogs"
If it means app.FileChooser,  I believe that lets the end-user pick (only) one file at a time.
If it means an Android Intent then do we have an example of the parameters (pkg, class, action, category, etc)?

app.QueryContent is a Premium feature
But what if a non-Premium developer wants to get a list of (say) images for a Gallery?

Regards, ah

mumumaia

unread,
Feb 6, 2023, 3:31:24 PM2/6/23
to DroidScript
toPath = app.GetSpecialFolder( "DCIM" )
app.MakeFolder( toPath + "/Folder")
Reply all
Reply to author
Forward
0 new messages