hBasic > hManual > Commands > SAF Commands

hBasic Manual
FILE.SAF Commands
FILE.SAF commands offer a limited interface into the Storage Access Framework.
The intention is to provide read/write access for SDcards that are External Secondary Storage.
e.g this could be a an sdcard slot for a removable sdcard.

These commands ONLY work for Android 5 (Lollipop / api 21) or later. Any lower version will fail silently (will not runtime-error). You can check for a phone's android version with Basic's DEVICE command (which requires the "make and manage phone calls " permission OR hBasic's DEVICE.OS command which does not require any permissions.

For any failed or unsuccessful command, there might be (or not) additional information in GetError$().
To see these messages automatically, you can turn on debugging mode with Debug.ON.

Since Android 11, Google has furthur limited SAF access. SAF can no longer access the top level directory of Download Android/data, Android/obb and root. In affect, the directories that you can access must pre-exist.

Uri commands


FILE.SAF.GRANT  Uri$ grants r/w permission, persistence
FILE.SAF.REVOKE {baseUri$} release permssion, persistence
FILE.SAF.CHECK rc {,baseUri$} check permssion,persistence
FILE.SAF.SET rc, Uri$ set the saf base directory
FILE.SAF.GET  Uri$  {,name$} get the saf base directory
Path commands


FILE.SAF.DIR path$, Array$[]  {,dirmark$} get a directory list
FILE.SAF.MKDIR rc, path$
create a directory
FILE.SAF.RENAME
rc, oldPath$, newName$ rename a file
FILE.SAF.DELETE
rc, path$ 
delete a file or dir
FILE.SAF.EXISTS
rc, path$
see if file exists
FILE.SAF.OPEN mode{t b r w a}, fileNum, SAF.path$
open a file for basic


FILE.SAF.GRANT    Uri$        grants r/w permission for SAF storage

A system dialog will appear listing the available SAF storage devices and directories/files if available.

Normally, a phone manufacturer will include a 'DocumentProvider' with the Android OS for removable sdcards, If so, then this would show up with the sdcard name on the first screen or a slide-in  'recent' or 'advanced' menu.

You should ask the user to select this name which is the root directory of the sdcard.
This action grants read/write permission from this directory and all sub-directories below it.
This action also persists the permission to survive reboots for the lifetime of the app (until the app is un-installed).

On Success :
Uri$ will be overwriten with the base uri of the saf drive.
This uri is a string and can be saved for future use (see FILE.SAF.SET).

On Failure:
Uri$ will be the empty string.


FILE.SAF.REVOKE    {baseUri$}            release permission,persistence

This is the opposite to GRANT. It removes permission and releases persistence for baseUri$.
If baseUri$ is not given, the current base uri is released.
There is no return code for this command.

Usually, persistence is released immediately.
For permissions, you may need to exit the app for it to be effective.
Normally, you don't need to revoke a saf grant, doing so means you have to re-grant the drive on the next access. Uninstalling the app automatically revokes any previous grant.


FILE.SAF.CHECK    rc  {,baseUri$}            check permission and persistence

Check if the baseUri$ has read+write permission and persistence for file access.

if baseUri$ is not given, the current saf base uri will be checked.

The numvar rc will be returned with the following return codes;

0 => has permission and has persistence
1 => has permission but no persistence
2 => no permission but has persistence
3 => no permission and no persistence
4 => bad uri
5 => not supported


FILE.SAF.SET    rc, Uri$            set the saf base directory

Sets the current saf base location to Uri$ (the uniform resource identifier).

The numvar rc will return;
0 => ok
1 => bad uri
5 => not supported
6 => could not set base
9 => other

FILE.SAF.GET    Uri$ {,name$}            get the saf base directory

Gets the current saf base location (a uniform resource identifier),  into Uri$.
Optionally, also get the directory name into name$.


FILE.SAF.DIR    path$, Array$[] {,dirmark$}    saf version of file.dir

Get a directory list of path$ into Array$[].
Optionally mark directories with dirmark$.

All paths are relative to the current saf base.
For the current directory (root of saf base) set to the empty string path$=""
For a sub-directory, set path$="subdir" or "/subdir" or "subdir/"  or "/subdir/"

Upon return, Array$[] will always have at least 1 cell.
Array$[1]
=> ""  (empty string)     means failed to get dir
Array$[1] => " " (one space only)    means the dir was empty.

If dirmark$ is not given. the default is directory marker is "(d)" .


FILE.SAF.MKDIR    rc, path$            saf version of file.mkdir

Creates a directory given by path$.

 path$ is relative to the current saf base.

e.g file.saf.mkdir    "docs/accounts/department_001"

The numvar rc will be returned with the following return codes;

0 => ok
1 => empty path
3 => invalid SAF base
4 => could not create
5 => not supported
9 => other

If the directory already exists, return code rc will be 0 (considered as success).
For any unsucessful return code, there may be additional info in GetError$().



FILE.SAF.RENAME    rc,oldPath$, newName$        saf version of file.rename

Rename a file on oldpath to newName.

All paths are relative to the current saf base.

e.g file.saf.rename    "docs/accounts/aaaa.txt" "bbbb.txt"

Note that oldname can include a path in front, while newName should not have a path at all.

The numvar rc will be returned with the following return codes;

0 => ok
1 => empty path
2 => invalid path or oldfile
3 => invalid SAF base
4 => invalid new name
5 => not supported
9 => other

FILE.SAF.DELETE    rc, path$            saf version of file.delete

Deletes a file or directory given the path$.

All paths are relative to the current saf base.

e.g file.saf.delete    rc, "docs/accounts/your_sub_dir.txt"

The numvar rc will be returned with the following return codes;
0 => ok
1 => empty path
2 => invalid path or file
3 => invalid SAF base
4 => could not delete
5 => not supported
9 => other


FILE.SAF.EXISTS    rc, path$            saf version of file.exists


Check if a file or directory exists given it's path$.

e.g  file.saf.exists    rc, "docs/accounts/your_sub_dir/something.txt"
        if rc then print "File Exists"

All paths are relative to the current saf base.

The numvar rc will be returned with the following return codes;
0 => not exist
else exists

Note that 0 (not exist) could be a false negative since any failure during the call will result as 0.
It is therefore recommended to check you have a valid saf base and path before usage.


FILE.SAF.OPEN    mode{ t b r w a}, fileNum, path$    saf version of text.open and byte.open

Open a file for Basic.

e.g file.saf.open tr ,fileNum, "docs/accounts/abc.txt"

This call returns a file number fileNum which can be used for other basic commands; e.g byte.read.byte. It is basic's internal file table entry index for the opened file.

path$ is relative to the current saf base.

mode
The open modes are t, b, rw or a ( without quotes ) in any combination, where;

t => text
b => byte

r => read
w => write
a => append

The default open mode is text+read tr.
For the mode word;
If b is found , it overrides t.
If w is found, it overrides r.
If a is found, it overrides both w and r.

To close a file, use the normal basic commands text.close or byte.close.



Leading Cloud Surveillance, Recording and Storage service; IP camera live viewing

Leading Enterprise Cloud IT Service; cloud file server, FTP Hosting, Online Storage, Backup and Sharing

Powered by FirstCloudIT.com, a division of DriveHQ, the leading Cloud IT and Cloud Surveillance Service provider since 2003.