Coexisting with Android

From RFO-BASIC! Manual
Manual contents (Statement index)
Language features The basicsArraysData structuresInterrupt routinesUser-defined functions
Interfaces Audio playerBluetoothFilesGraphicsHTMLKeyboardSocketsSQLTelecom
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.

Conserving battery[edit]

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

Screen always on[edit]

Android devices shut off the screen after a certain number of minutes without sensing a screen touch. Android has a display setting that lets the user select the interval; "forever" is not a choice. (Android has a "developer setting" to keep the screen on permanently, if the device is recharging or charged, but it will still dim after the set interval.)

RFO-BASIC! programs engaged in monitoring, playing music, or driver assistance do not want the screen to ever shut off. The solution is the WAKELOCK statement.

WAKELOCK[edit]

Modify Android's time-outs

Syntax
WAKELOCK <code_nexp>{, <flags_nexp>}
Description

The WAKELOCK statement modifies Android's time-outs. The <code_nexp> parameter can have one of five values:

Code Power button Screen Keyboard light
1 (Partial) Program continues to run; see below Off Off
2 (Dim) Puts device to sleep Dim Off
3 (Bright) Puts device to sleep Bright Off
4 (Full) Puts device to sleep Bright Bright
5 (No wakelock) All controls function normally

The optional <flags_nexp> parameter specifies additional behavior:

Value Effect
1 Turn the screen on when the program acquires the wakelock (except partial wakelocks).
2 Reset the screen time-out when the program releases the wakelock (using WAKELOCK 5).
3 Both effects
Any other value, or omitted Neither

When the program ends, RFO-BASIC! releases any wakelock the program had called for. This begins the user-specified interval; if the device remains idle for that interval, the screen shuts off.

Partial wakelock

In the partial wakelock (specified by WAKELOCK 1), the screen and any keyboard light switch off, but the device continues to run for as long as the RFO-BASIC! program remains active. If the program has nothing else to do, or if all the actions it will take are in interrupt routines, then the mainline can use a wait loop such as the following:

WAKELOCK 1
DO
 PAUSE 1000
UNTIL 0

In a partial wakelock, it is meaningless to use the <flags_nexp> parameter. If the program should become totally inactive (for example, PAUSE for longer than the user-specified inactivity interval and have no interrupts), the device will go to sleep. If the program ends or exits, the wakelock is released, as usual.

Use with other statements
  • The OnBackground: routine and the BACKGROUND() function let the program react to the loss of access to the screen. If the program uses WAKELOCK to turn the screen off, it loses access to the screen and this handler receives control.
  • A program may acquire a wakelock to keep the device fully active if the device is plugged into power, but let the screen shut off after the interval the user specified if the device is running on batteries. Detecting the power situation is provided as an example, below.

Sensors[edit]

Android devices have built-in sensors. They measure a variety of ambient conditions, such as light level, temperature, humidity, and air pressure; and use of the device, such as gravity, orientation, motion, and acceleration. Some sensors measure raw readings, while others return readings involving computations by the Android device.

The RFO-BASIC! program specifies a sensor using a number. It must use SENSORS.OPEN to "open" the sensor, because use of some sensors requires that the Android device apply power to a sensing circuit, which should be done only if the program requires such input. Once opened, the program can use SENSORS.READ to obtain up to three numeric readings from a specified sensor. The program can use SENSORS.CLOSE to deactivate all sensors, possibly conserving electric power. Otherwise, the sensors are closed when the program stops.

Not all Android devices have all the sensors. Newer devices tend to have more types of sensor. The programmer should write a test program with #SENSORS.LIST to determine which sensors are present on the target device.

For the possible types of sensor, and the meanings of each of the three numbers the sensor returns to the program, see the Android SensorEvent documentation.

SENSORS.LIST[edit]

Enumerate the types of sensor available on the device

Syntax
SENSORS.LIST <sensor_array$[]>
Description

The SENSORS.LIST statement returns, into the one-dimensional string array <sensor_array$[]>, information on each type of sensor that is available on the device. If this array exists, information is written into it and any information previously in the array is deleted. Otherwise, the SENSORS.LIST statement creates the array and writes to it.

Each element of the string array receives information on one sensor, in a human-readable format. To determine how many sensors the device has, the program can get the number of array elements by using ARRAY.LENGTH.

A typical array element might be: Gyroscope, Type = 4. If RFO-BASIC! detects a type of sensor but does not know what it does, it might return an array element such as: Unknown, Type = 399.

Example

This program prints all available sensors to the text console:

SENSORS.LIST sensorarray$[]
ARRAY.LENGTH size,sensorarray$[]
FOR i = 1 TO size
 PRINT sensorarray$[i]
NEXT i
END

SENSORS.OPEN[edit]

Open one or more sensors for use by the program

Syntax
SENSORS.OPEN <type_nexp>{:<delay_nexp>}{, <type_nexp>{:<delay_nexp>}, ...}
Description

The SENSORS.OPEN statement opens one or more sensors for reading. Opening a sensor may involve applying electric power to a circuit within the Android device. To minimize battery drain, an RFO-BASIC! program should only open the sensors it will need to read.

The program can open one sensor, specified by number, or a list of sensors, separated by commas. For each sensor, the program can have : (see the sidebar) followed by a <delay_nexp> that specifies how frequently to poll the sensor. Whether a device obeys this delay depends on the device and on the sensor type. The programmer should assume that faster polling of a sensor will increase the draw on the battery, and use the highest number (longest delay) that does not produce undesirable pauses. The value of <delay_nexp> must be one of the following:

Value Name Typical delay Typical usage
3 or omitted Normal 200 msec Suitable for sensors that need only be tested if the user rotates the screen orientation
2 UI 60 msec Suitable for sensors that should be tested any time the user does anything
1 Game 20 msec Suitable for gaming
0 Fastest 0 msec No delay at all; the sensor is sampled as fast as possible
Example

The following piece of a program opens two sensors: the Acceleration sensor (type 1) at a rate suitable for gaming, and the Orientation sensor (type 3) at the normal rate.

SENSORS.OPEN 1:1,3

SENSORS.READ[edit]

Read the information from one sensor

Syntax
SENSORS.READ <sensor_type_nexp>,<p1_nvar>,<p2_nvar>,<p3_nvar>
Description

The SENSORS.READ statement returns the latest values from the sensor specified by <sensor_type_nexp>. The sensor of this type must have been opened by using SENSORS.OPEN. The statement places numeric values into <p1_nvar>, <p2_nvar>, and <p3_nvar>. Each SENSORS.READ statement must include all three of these variables, even if the specified sensor does not use them. The statement sets unused variables to 0.

Example

The following code gets a compass reading (Type 2) in the X, Y, and Z dimensions.

SENSORS.OPEN 2:2   %Assume the user wants fast update on rotating the device
SENSORS.READ 2, CompassX, CompassY, CompassZ
Usage

RFO-BASIC! polls sensors at a frequency specified by the SENSORS.OPEN statement for that sensor. The program should execute SENSORS.READ at about the same frequency, though there is no way to synchronize with the polling. For example, executing SENSORS.READ continually on a sensor that is polled only every 200ms is wasted computing, as most executions will return the same value as before. Likewise, if the program executes SENSORS.READ only every ten seconds, it is a waste to have SENSORS.OPEN specify faster polling than the minimum.

SENSORS.CLOSE[edit]

Disable all sensors

Syntax
SENSORS.CLOSE
Description

The SENSORS.CLOSE statement closes all sensors that the program has opened with SENSORS.OPEN. It is not an error to call SENSORS.CLOSE if the program has not opened any sensors. Closing sensors typically reduces the drain on the battery by cutting electrical power to the affected sensors.

If a program opens one or more sensors and does not close them with SENSORS.CLOSE, RFO-BASIC! automatically closes them when the program stops.

RFO-BASIC! has no way to close only selected sensors. However, the program is free to call SENSORS.OPEN again on some of them. A program might close-then-reopen to minimize battery drain if the user chooses a dialog that requires readings from additional sensors, and then completes that dialog and the program no longer requires them.

Android command interface[edit]

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.

SYSTEM.OPEN[edit]

Open a shell

Synopsis
SYSTEM.OPEN
Description

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.

SYSTEM.WRITE[edit]

Write a command to the shell

Synopsis
SYSTEM.WRITE <command_sexp>
Description

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.

Example

The following code requests a listing of a certain directory:

SYSTEM.WRITE "ls -al " + DirectoryName$

SYSTEM.READ.READY[edit]

Poll whether the shell has responded

Synopsis
SYSTEM.READ.READY <ready_lvar>
Description

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.

Example

The section Example: Battery query contains an example of waiting a maximum of half a second for the shell to respond.

SYSTEM.READ.LINE[edit]

Obtain one line of a response from the shell

Synopsis
SYSTEM.READ.LINE <line_svar>
Description

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.

SYSTEM.CLOSE[edit]

Close the shell

Synopsis
SYSTEM.CLOSE
Description

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.

SU statements[edit]

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[edit]

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, /Android/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$ = "/Android/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[edit]

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 BatteryQuery$("status") returns Discharging or 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.

Notifications[edit]

Notifications are small graphics that appear in the status bar at the top of the Android screen. Pulling down from the top reveals a more detailed description. RFO-BASIC! programs can produce notifications as part of their user interface.

NOTIFY[edit]

Generate an Android notification

Syntax
NOTIFY <title_sexp>, <subtitle_sexp>, <alert_sexp>, <wait_lexp>
Description
Above the running program's text console, reaching the NOTIFY statement (see Example) produces an initial Notification at the top of the screen.
Swiping down from the top shows the detailed Notification produced by this example.

The NOTIFY statement generates a Notification on the Android device. The graphic used is the stars-and-telescope RFO-BASIC! logo, except that, for user-built apps installed from an APK file, it is whatever application icon the builder specified. (Newer Android versions use a generic exclamation point inside a circle on the status line.)

When the statement is executed, RFO-BASIC! displays the graphic and the alert message contained in <alert_sexp>. If the user pulls down the Notification area, it will contain the title from <title_sexp> and the subtitle from <subtitle_sexp>.

If <wait_lexp> is true (nonzero), then the execution of the RFO-BASIC! program is suspended until the user pulls down the Notification area and taps the program's Notification. If <wait_lexp> is false (zero), then the RFO-BASIC! program keeps running and tapping the Notification has no effect.

Example
PRINT "Executing NOTIFY"
NOTIFY "RFO-BASIC! Notify", "Tap to resume program", ~
  "RFO-BASIC! Notification", 1
!Execution is suspended until the user taps the Notification object
PRINT "NOTIFY acknowledged"
Multiple simultaneous notifications

When <wait_lexp> is false, the program proceeds. Thus it is possible to have multiple Notifications that do not wait for user acknowledgement. On the pulldown list, the most recent Notification from the program supersedes any older ones. Whether there is one or several audible alerts depends on the device and Android version; multiple Notifications in a short period of time may produce only one audible alert. The program can avoid this behavior using the PAUSE statement.

Dismissing the notification

Rather than tapping the notification, the user may dismiss it. (In the Android 8 illustration shown above, the user would tap CLEAR.) If <wait_lexp> is true, specifying that the program should not resume until the user taps the notification, the program does not resume at all if the user dismisses the notification.

In this case, if the user has used Preferences to enable a menu on the text console, that menu has an Exit entry that the user can use to regain control of RFO-BASIC! and return to the Program Editor (but not resume the program from the NOTIFY statement). Otherwise, the user must use the Recents button to terminate the RFO-BASIC! task.

Sources[edit]