Coexisting with Android
|Manual contents (Statement index)|
|Language features||The basics • Arrays • Data structures • Interrupt routines • User-defined functions|
|Interfaces||Audio player • Bluetooth • Files • Graphics • HTML • Keyboard • Sockets • SQL|
|Programming notes||Coexisting with Android|
An RFO-BASIC! program runs on an Android device that is doing many other things — such as maintaining contact with a WiFi or Bluetooth network or a cell tower — and may be running under battery power that the program should take care to conserve.
Computing requires electric power; the BASIC program can use less power by spending more time in the PAUSE statement. Unfortunately, each execution of PAUSE is for a specific interval. The interval can be a variable or expression but it cannot adapt to things that happen during the interval. Moreover, PAUSE cannot be interrupted.
PAUSE totally suspends BASIC, not just execution of statements but things that happen in parallel, such as monitoring of keystrokes. A pause longer than a second (PAUSE 1000) may give the impression that the program has crashed, and may make the program lose keystrokes. For programs that do not rely on user input, PAUSE 5000 may be feasible. The program's operations are inside a main loop with the following form:
DO <BASIC statements> PAUSE 5000 UNTIL 0 % That is, forever
Shorter pauses increase responsiveness to real-time events such as screen touches and key presses. If the main loop needs executing only every five seconds, the program can be more responsive to real-time events such as screen touches and key presses by waking every 0.1 second during the desired interval. This awakening not only permits interrupts but gives the program a chance to break out of the pause loop. For example:
DO <BASIC statements> FOR I = 1 TO 50 PAUSE 100 IF <something happened that requires main-loop processing> THEN F_N.BREAK NEXT I UNTIL 0 % That is, forever
Battery usage depends on the ratio of time spent computing, to time spent pausing. A typical loop with PAUSE 100 spends almost all of its time in PAUSE. Increasing the pause time further does not change the ratio substantially.
Gaming and measurement programs may require that the main loop execute at a constant rate. In this case, the PAUSE statement should deduct time spent in the main loop (but not specify a negative amount of time):
DO InitialTime = CLOCK() % Milliseconds since most recent start-up <BASIC statements> TimeSpentComputing = CLOCK() - InitialTime PAUSE MAX(100 - TimeSpentComputing, 1) UNTIL 0 % That is, forever
Android command interface
RFO-BASIC! runs on smartphones that use Android, a Linux-based operating system with modifications by Google. It is a powerful technique to use BASIC to feed text commands directly to the operating system. The BASIC statements in this section open a command shell. This is an environment with a saved context; the context remembers the results of a given command so that they affect subsequent commands. For example, if you change the working directory of a shell, the change remains in effect for subsequent statements.
The RFO-BASIC! program must open the command interface before operating on it, and should close it when operations are complete. The command interface works by sending string expressions and then waiting for responses to be placed, one line at a time, into string variables. The time this takes is device-dependent. The shell does not respond to some commands at all, so the BASIC program should loop and be ready to time-out and proceed with other behavior if the shell does not respond.
The SYSTEM statements use a shell to the Android operating system. The SU statements use a shell with superuser privileges. Only one can be active at any time.
Open a shell
The SYSTEM.OPEN statement opens an Android command shell. The BASIC program must open the shell before issuing commands to it. The statement fails if there is already a SYSTEM or an SU shell open.
The shell's working directory is set to the RFO-BASIC! installation directory, such as rfo-basic. Depending on the RFO-BASIC! preferences, this may be in the phone's internal memory or on an external card.
Write a command to the shell
The SYSTEM.WRITE statement sends the command in <command_sexp> to the command shell. The command does not need to end with a line terminator.
The following code requests a listing of a certain directory:
SYSTEM.WRITE "ls -al " + DirectoryName$
Poll whether the shell has responded
The SYSTEM.READ.READY statement determines if the command shell has a response that the BASIC! program can read using SYSTEM.READ.LINE (below). If the shell has not responded, <ready_lvar> is set to 0. If the shell has responded, it is set to a nonzero value.
The section Example: Battery query contains an example of waiting a maximum of half a second for the shell to respond.
Obtain one line of a response from the shell
The SYSTEM.READ.LINE statement sets <line_svar> to the next available line in the command shell's response. The returned string does not include a line terminator.
The BASIC program should use SYSTEM.READ.READY statement to see if a response is available. If a response is not available, SYSTEM.READ.LINE sets <line_svar> to an empty string.
If SYSTEM.READ.READY indicates that the command shell has responded, then SYSTEM.READ.LINE can be called repeatedly, without further polling or pauses, to retrieve each line of the response in sequence. The BASIC program should be written to take into account how many lines a command will return, or continue calling SYSTEM.READ.LINE until it returns an empty string or a string known to be the last line of the response. Some commands return blank lines; these also cause SYSTEM.READ.LINE to set <line_svar> to an empty string.
Close the shell
The SYSTEM.CLOSE statement closes the command shell. This discards the shell's environment and context. Opening a new shell, with the SYSTEM or SU statements, will not have any information from a previous shell, unless the program has saved it, for example, in a file or database.
RFO-BASIC! also provides matching statements beginning with SU rather than SYSTEM:
SU.OPEN SU.WRITE SU.READ.READY SU.READ.LINE SU.CLOSE
These are identical to the SYSTEM statements except that:
- The initial working directory is the root directory, /.
- The resulting command shell has superuser privileges.
Some devices do not allow the SU.OPEN statement to execute successfully. In this case, the statement fails, issuing an error message such as the following:
SU Exception: Cannot run program "su": error=13, Permission denied
Example: Interfacing to a C program
BASIC statements are interpreted rather than translated into processor code. Interpretation is relatively time-consuming. The processor in a phone is so fast that this usually does not result in noticeable pauses.
However, for calculations involving thousands of repetitions, a BASIC program may rely on an external program written in a compiled language such as C. This tutorial describes how to do so. The more complicated part is producing the external program in the first place; the tutorial describes using the Android Native Development Kit (NDK) and building the external program (the "binary").
Assuming the binary exists, the BASIC program starts a command shell and changes the current directory to BASIC's private storage directory on the phone,
/data/data/com.rfo.basic. The binary must reside here so that Android will execute it:
Package$ = "com.rfo.basic" % RFO-BASIC!'s package name path$ = "/data/data/" + Package$ + "/" SYSTEM.OPEN SYSTEM.WRITE "cd " + path$ + " 2>&1" PAUSE 50
If the binary is in RFO-BASIC!'s data directory (
rfo-basic/data), use the cat statement to copy it over to the current directory (BASIC's private storage directory), into a file by the same name, then use chmod to make the binary executable:
BinaryName$="main" % Name of binary program FILE.ROOT BasDataDir$ % Get name of BASIC data dir BinaryPath$= BasDataDir$ + "/" + BinaryName$ SYSTEM.WRITE "cat " + BinaryPath$ + " >./" + BinaryName$ + " 2>&1" SYSTEM.WRITE "chmod 777 " + BinaryName$ + " 2>&1"
Now you execute the binary by sending its name to the shell:
SYSTEM.WRITE "./" + BinaryName$ + " 2>&1"
Receive the binary's output, adding a line terminator (
\n) to each line:
Response$ = "" % Assume no response PAUSE 1000 % Wait 1 second SYSTEM.READ.READY ready WHILE ready % If there is a response, SYSTEM.READ.LINE l$ % Get 1 line at a time Response$ += l$ + "\n" % Accumulate them in Response$ SYSTEM.READ.READY ready REPEAT PRINT Response$
Instead of printing the result, the BASIC program may analyze it and, for example, display the results graphically. Finally, clean up as follows:
SYSTEM.WRITE "rm " + BinaryName$ % Delete the binary SYSTEM.CLOSE % Close the shell
Example: Battery query
On devices such as Samsung phones, the SYSTEM statements let programs query the power status of the Android device, using code such as this:
FN.DEF BatteryQuery$(q$) SYSTEM.OPEN SYSTEM.WRITE "cat /sys/class/power_supply/battery/" + q$ StartTime = CLOCK() DO SYSTEM.READ.READY r UNTIL r | CLOCK() - StartTime > 500 SYSTEM.READ.LINE out$ SYSTEM.CLOSE FN.RTN out$ FN.END
A call to
Not charging when the Android device is plugged into external power. A call to
BatteryQuery("capacity") returns the percentage of battery charge.
When the Android device is drawing external power, there is no need to conserve power at the expense of responsiveness, as the total power used is tiny. When the device is operating from battery, there are many ways to conserve power, including longer pauses, reducing screen illumination with a statement such as
GR.BRIGHTNESS 0.25, or omitting the use of user-friendly features such as confirmation with text-to-speech.