Frink Documentation * What's New * FAQ * Download * Frink Applet * Web Interface * Sample Programs * Frink Server Pages
This page only contains information about Android-specific features of Frink. For more information about Frink, please view the full Frink documentation.
The Frink app for Android is available (for free!) from several places:
frink.apk
file here.
frinkold.apk
Frink is available for the Android platform, as well as for your other computers (Mac, Linux, Windows, etc.) The Android version provides a wonderful marriage between an easy-to-use programming language and a powerful, standardized platform with real-world sensors and other interesting features. It should work on all Android platforms version 1.5 and later. This is a full port of all of Frink's features, not a limited subset. (For a full list of Frink's features see the Features section of the main documentation.) The Android-specific features include:
frink
(in other words, this is often mounted
under /sdcard/frink
, or on later Android devices,
under /mnt/sdcard/frink
.)
speak[string]
function. (Currently requires
Android 1.6 or later.)
listen[prompt]
function. (Then it can
translate that text into other languages, speak it aloud, and perform
calculations with it!)
Although great care has been made to use only functions that appear in Android 1.5, other functions are loaded by name to give access to sensors, GPS, and other later features.
The Frink application currently requests permissions for the following:
read[URL]
.
Note that Frink doesn't use any of these unless a program you write or run uses one of the features listed. Frink doesn't contain any advertisements or tracking of any kind.
In the future, versions of Frink might request almost all
permissions for people who want to write programs that can access all of
their phone's features. I may also maintain a limited version that
doesn't request any permissions for the security-paranoid. (This
will make features not work.) The internal Frink code will be
exactly the same; it just differs in what I request in
the AndroidManifest.xml
file.
As you may know about packaging Android applications, if you want access
to certain features of the phone, then you must request access to
those features in the AndroidManifest.xml
file when
packaging. Even if you never use them, or don't use
them now, if anyone wants to write programs that access
these features, the package has to request access to them. There's no way
to select just the features you want to enable, which is a failure of
Google's security design and implementation.
I could create a crippled version of Frink that requests no permissions (but it won't be able to do lots of interesting things), and another version that requests all permissions so that power users can do whatever they want with their phone, without restrictions. It's likely, though, that if you're writing programs, you're some sort of programmer that understands the Android security model. And you hardly want me, or Google, or your wireless carrier, to tell you what you can or can't do with the phone you paid for. The point of Frink is to free you to do anything you can conceive, and not to hobble you arbitrarily.
By the way, I have no intention of creating a million distributions of Frink, each with a different permutation of requested permissions.
I wish that Android (and Java) had designed their security managers better; I'd always much prefer that a program only bother you with security requests when it actually uses them, so as not to create false scares. You could then choose something like "Allow this time only / Allow always / Don't allow this time / Never allow." And at that time, it could even give you usable information, like tell you what sites it's sending data to, what files it's opening, what phone information it's accessing, etc. That would give the user more flexible, fine-grained control over what their device is doing at any given time. It would also allow you to have better control over questionable apps. But that's nothing that anyone but Google can fix.
If I don't request permissions for features, programmers will never be able to access them. If there's a permission you need enabled to do your work, please let me know.
Update: As of Android 7, you now have more ability to revoke specific permissions for applications, and the operating system may prompt you to confirm access for applications that request troublesome permissions. Finally. (My old Java-based flip-phone from 2004 still did this better, though.)
The following is a list of the additional functions that are available on the Android platform.
Function | Description |
---|---|
Text-to-Speech | |
speak[expression] | Speaks the text aloud,
using your device's default language settings.
Note that, depending on the text-to-speech engine installed on your
device, it may be possible to send out fragments of Speech
Synthesis Markup Language (SSML, link opens in new window) to
modify pronunciation, intonation, etc. See notes for the
While this may not work properly in all voice engines, you can try to
force the engine to speak in different languages by sending out a
top-level SSML
Andrew Webb has put together an extensive table of SSML compatibility of various speech engines. |
speakln[expression] | Speaks the text aloud,
using your device's default language settings, and also
performs a println of the same text.
|
speakSAMPA[string] | Speaks the text aloud,
treating the text as if it is specified phonetically using the Extended SAMPA alphabet. (Link opens in new window.)
This allows you to specify the exact pronunciation of text. For
example, to speak my name more or less correctly, you can try:
Note that the quote mark Also note that your Android device may have one of many text-to-speech engines installed. This is known to work with the Pico engine or the SVOX Classic Text to Speech Engine, but it's even trickier than that. Each of these engines may have multiple voices installed or available, and not all phonemes are available in all voices. |
encodeSAMPA[string] | Takes a string in the Extended
SAMPA (link opens in new window) alphabet and wraps it into an
approprite SSML fragment to make some text-to-speech engines pronounce it
correctly. For example, if you just want to correct the pronunciation of
one of the words in your utterance, you can do something like:
However, please see the caveats in the |
Speech Recognition | |
listen[prompt] | Listens for voice input,
prompting the user on-screen with the specified prompt. The spoken text
will be returned as a string, or the special value undef
will be returned if voice input is cancelled. Note that on most
devices, you can select between many, many languages for input on the
voice input screen!
Note that the |
listen[prompt, numOptions] | Listens
for voice input, prompting the user on-screen with the specified prompt.
The user will then be presented with a list of not more than
numOptions options of what was possibly heard, allowing the
user to select the appropriate text. If only one possible utterance was
recognized, or if numOptions is 1 or 0, the user will not
be prompted to select the appropriate string. The selected text
will be returned as a string, or the special value undef
will be returned if voice input is cancelled.
|
GPS / Location | |
getLastKnownGPSLocationJava[] | Returns the last
known location from the GPS provider as a Java object of
type Location .
Note that this does not start the GPS receiver and obtain a
fix, but uses the last-known location from when the GPS receiver was
previously enabled.
|
getLastKnownNetworkLocationJava[] | Returns the last
known location from the network-based location provider as a Java object
of type Location .
|
startGPS[] | Starts the GPS receiver with good default settings for update frequency (1 second) and update distance (0 meters, meaning you'll get updates even if no motion is made.) If you want to save power by updating GPS less frequently, see the function below. |
startGPS[minTime, minDist] | Starts the
GPS receiver with the specified time minTime for update frequency
(this must be a unit with dimensions of time like "1 second") and update
distance minDist (this must be a unit with dimensions of length
like "0 meter".) minDist should probably be "0 meter" because
you likely won't get position updates as often as you should otherwise,
as this parameter doesn't make a lot of sense and seems to be broken on
most Android devices.
Setting these values to non-default values allows you to potentially save some power on your device by activating the GPS receiver less frequently, but you probably don't want to use this version of the function without testing on a variety of devices. Note that Android devices before Android version 4.1 could ignore these parameters. For more information about how these parameters should be interpreted by your android device, see the Android documentation here. Note that the documentation states that Android versions before 4.1 (Jellybean) could ignore these settings. |
getGPSLocationJava[] | Returns the current-known GPS
position as a Java object of
type Location if
one is available. Note that startGPS must be called before
this will work. This function may return undef if a
position has not yet been acquired. You will most likely not get
useful data immediately. Typical usage is to poll this function
every few seconds using sleep statements, but this is not
very efficient nor reliable. A better option is to use the method
below.
|
waitForGPSLocationJava[] | Returns the current-known
GPS position as a Java object of type Location .
Note that startGPS must be called before this will work.
If a new, unread GPS location is available, this returns it
immediately, otherwise it blocks until the next GPS location is
available. It should not return data until GPS locations are being
returned. This may block forever if GPS is not available!
|
stopGPS[] | Stops the GPS receiver. The GPS receiver will be automatically stopped when the program exits, but this should be called as soon as the GPS is no longer needed to preserve battery life. |
getGeocoder[] | Returns
a Geocoder
object which can be used to turn latitude/longitude information into a
street address, or vice-versa.
|
getLocationManager[] | Returns the Android platform's
LocationManager
as a Java object. This allows you to access location-specific services
that are not available through the easier-to-use GPS functions listed
above.
|
Sensors | |
getSensorService[sensorType] | Starts collecting data
from the default sensor of the specified sensor type and returns a
SensorService object that can be
used to get raw values from the sensor. The sensor type should be one of
the integer constants defined in Sensor
If you want to control the frequency with which data is returned, use the
two-argument function below.
|
getSensorService[sensorType, updateFrequency] | Like the method above, but allows you to
control the frequency with which data is returned.
|
getSensorManager[] | This function returns the
Android device's SensorManager
as a Java object. This allows you to access low-level sensor properties
that are not available through the easier-to-use GPS functions listed
above.
For example, to obtain a list of sensors in your system, you could call:
where type is one of the sensor constant types defined in the
|
Programming | |
getActivity[] | Returns the
Android Activity
of the current-running activity as a Java object. Note that
an Activity is
a Context ,
so in all the places you need a Context (say, to create GUI
objects or get services,) you can use this object.
|
doUI[expression] | Executes the specified expression on the Android's User Interface (UI) thread. Android requires that many methods, including anything to create views or user interface components, is executed on the UI thread. This allows you to create objects like Toast or access other user interface elements. |
A SensorService
is an Android-specific object type that
allows you to get raw values from and Android device's sensors.
A SensorService
object for the specified sensor type is
obtained from calling the getSensorService[sensorType]
or getSensorService[sensorType, updateFrequency]
function. The sensor type is an integer that corresponds to one of the
sensor type constants defined in Sensor
. If using the version that takes a updateFrequency
parameter, this must be an integer from 0 to 3, 0 being the fastest, and 3 being normal (slowest). These correspond to one of the SENSOR_DELAY_... constants defined in Android.
The SensorService
has the following methods:
Method | Description |
---|---|
getSensorName[] | Returns the name of the sensor. |
getType[] | Returns an integer representing the type
of the sensor. The integer corresponds to one of the constants defined
in the Sensor class.
|
getRawValues[] | Returns the latest raw values from
the sensor as an array of floating-point values without dimensions and
returns immediately. This function is probably used with periodic
polling using sleep statements, so it's not very efficient,
and you may drop available values if polling too slowly, or get repeated
values if polling too often. See the next method for a better option.
The raw values should be interpreted according to the sensor type. The official Android documentation shows how to interpret these values. |
waitForValues[] | Returns the latest raw values from the sensor as an array of floating-point values without dimensions. If new, unread values are available, this returns them immediately, otherwise it blocks until new values become available. This is much preferable to polling. |
getAccuracy[] | Returns an integer indicating the accuracy of the value returned by the sensor. This integer is one of the SENSOR_STATUS_ACCURACY_... constants defined by the Android framework. These numbers are likely 0 (unreliable), 1 (low accuracy), 2 (medium accuracy), and 3 (maximum accuracy). |
In programming mode, if a file has been saved, its directory will be added
to the search path
for use
statements.
In interactive mode, or in programming mode where a file has not been
saved yet, the search path will include the frink
subdirectory on your removable media (SD card.) (The mount point as
seen from your Android device is usually /sdcard/frink
, or on
later Android releases, /mnt/sdcard/frink
).
It can be cumbersome to write programs on a handheld device, so here are a few tips that might make the process easier:
.apk
file download.
.frink
file
extension) directly in Frink's editor. (If they don't open these files
directly, or say something about an "unknown file type" they're not a
real file manager. Android has a well-defined system for opening files
using Intents, and Frink registers itself to edit files that end with the
.frink
file extension. See
the Viewing Files section of this document
for more.)
frink
subdirectory on your
removable media (SD card.) Put files there. (The mount point as seen
from your Android device is usually /sdcard/frink
, or on
later Android releases, /mnt/sdcard/frink
)
use
of that URL. For example:
use http://futureboy.us/frinksamp/animate.frink
Then just transfer the file permanently to your device when you're done.
frink
subdirectory to give you
samples to play with, and useful libraries for astronomical
calculations, navigation, and more.
Files with the extension .frink
can be viewed (and then
optionally run) from applications like Astro, or other file managers, or
viewed with an Intent
with the ACTION_VIEW
action. (the data must be a Uri
with a file:
URL, or on Android 6 and later, a
content:
URI.) This allows you to, say, make a folder of
Frink programs on your Android device that can be opened with a single
click.
Be warned that lots of "file managers" on Android don't ever actually try to view registered file types that your Android device knows perfectly well how to view. If they say something about "unknown file type" then they probably haven't even tried. They probably just looked at some internal sad list of types and decided they'd give up on trying to actually view the file according to the Android specification.
The following restrictions on viewing files are for security reasons:
.frink
extension will be viewed only if
they are on the filesystem. Files with the .frink
extension
will never be viewed from http
URLs nor from the
network.
On the android platform, an Intent
is a class that is used to trigger
actions in other applications on your phone, such as sending
e-mail messages, SMS messages, dialing phone numbers, starting or querying
other applications, and more. It's the way you can make other applications
do what you want.
You can send an arbitrary Intent
with a few lines of Frink code.
You can learn about the full complexity of intents from the Android Intent
documentation and the Android Intent Tutorial.
The following examples demonstrate creating and sending an Intent.
The following example demonstrates the basics of sending an Intent
that will trigger the sending of
e-mail or SMS messages. See the Android Intent Tutorial for more discussion on what's
happening here, and additional options that are available.
ic = "android.content.Intent"
i = newJava[ic] // Create an Intent
i.setAction[i.ACTION_SEND]
i.putExtra[i.EXTRA_TEXT, "This is the email body"]
i.putExtra[i.EXTRA_SUBJECT, "Sent from Frink on Android"]
i.putExtra[i.EXTRA_EMAIL, ["your@email.address", "another@email.address"]]
i.setType["text/plain"]
// Now call android.content.context.startActivity(Intent)
getActivity[].startActivity[i]
This uses Frink's Java Introspection capabilities to call arbitrary code. Also note that this code will probably bring up a chooser on your device to allow you to choose how to send the message, including e-mail, SMS, or more.
See the Android Intent Tutorial for more samples that demonstrate how to add attachments, binary data, etc.
Note that since we have an instance of an Intent
object, we can use constants like EXTRA_TEXT
with little
effort. However, if we need a static field from another class, we could
have used the slightly more verbose staticJava[classname,
fieldname]
call to look up the fields:
ic = "android.content.Intent"
i = newJava[ic]
i.setAction[staticJava[ic, "ACTION_SEND"]]
i.putExtra[staticJava[ic, "EXTRA_TEXT"], "This is the email body"]
...
Please keep in mind that Android devices have limited memory! While Frink tries hard to warn you of out-of-memory conditions, there's little or nothing reliable that you can do when no memory is available. Frink will try very hard to put up a warning if you run out of memory, but that is not always going to be reliable. If the Frink application simply silently disappears, that's an almost-certain indication that it ran out of memory. If this happens, make sure that the Frink application is stopped completely, and then try again.
You may need to make more RAM available to execute programs.
Note that RAM is not the same as free space on your slow SD card, but is the small, fast memory available to run programs (usually 256 MB or less,) which must be shared between all installed applications, running programs, and running services on your device.
(Your SD card may be large, but it can't be used for working memory in programs. It can only be written in large blocks, and takes thousands or millions of times longer to write than RAM, and can only handle a limited number of writes in its lifetime.)
The Android operating system will also limit the memory available to each process (usually to 16 MB or less, which is a fundamental limit for any Android program.) If you're hitting this limit, you may be unable to run your program on any Android device. (But you can still run Frink programs on your PC.) It's the nature of small, memory-constrained devices.
To free up memory, you may need to stop some running applications or background services, or uninstall unused applications to free up RAM.
In addition, stack size is very limited on Android devices. (By default, many platforms only allocate 8 kB of stack space.) In the programming mode for Frink on Android, the settings menu will allow you to request a larger stack space. Setting this value too high or too low may cause issues. An Android device is also not required to respect your request for more stack space. If you have problems running highly-recursive problems, you may be running out of stack space. Note that you must fully exit Frink and restart it for the new stack size to take effect!
Comments/questions to Alan Eliasen.