As we already learned, the PilBox is a tool providing an interface to the Android Java runtime environment. Specifically, we are able to use most of the classes that are listed in the official Android documentation: developer.android.com/reference/classes.html
Often, the interesting part of a smartphone app needs to acces smartphone-specific resources (like camera, GPS..). In this case, we need to leave our PicoLisp environment and send an intent to an app which provides the needed resources. This post will cover a general explanation of intents and how we can call them from PicoLisp using the Java Interface.
Android Intents
Intents are messaging objects that can be used to request an action from another app component such as camera, GPS, contacts and so on. There are three fundamental uses cases:
- starting an activity, which is represented by a single screen in an app,
- services, which run in the background,
- or broadcast deliveries: messages that any app can receive.
There are two types of intents:
- Explicit intents specify which application will satisfy the intent, by supplying either the target app's package name or a fully-qualified component class name.
- Implicit intents do not name a specific component, but instead declare a general action to perform, which allows a component from another app to handle it.
Obviously, implicit intents are inherently less secure because there is no control from developer side which app will accept the intent. The Android system will simply find all appropriate components based on the intent filters in the manifest files of other apps.
From the android docs: 1. How an implicit intent is delivered through the system to start another activity: [1] Activity A creates an Intent with an action description and passes it to startActivity(). [2] The Android System searches all apps for an intent filter that matches the intent. When a match is found, [3] the system starts the matching activity (Activity B) by invoking its onCreate() method and passing it the Intent.
Now let's see how we can call intents from our PicoLisp app.
Example: Camera takePictureIntent
as per Android docs
A typical intent is a call to take a picture from the smartphone app. Let's look at the example Java code from the Android documentation.
First, we need to check if the camera is available:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
Then we can call the intent, which involves three pieces: The Intent itself, a call to start the external Activity, and some code to handle the image data when focus returns to your activity.
static final int REQUEST_IMAGE_CAPTURE = 1;
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
try {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
} catch (ActivityNotFoundException e) {
// display error state to the user
}
}
Now let's see how we can call this from PicoLisp.
The java
function
Let's take a look at lib/android.l
in your local pil21-installation (or in the PilBox source code). Here we find the definition how to access the Java environment from PicoLisp in line 37 to 45:
# Java I/O
# (java "cls" 'T ['any ..]) -> obj New object
# (java 'obj ['n] 'msg ['any ..]) -> any Send message to object
# (java 'obj "fld" ['any]) -> any Value of object field
# (java "cls" ['n] 'msg ['any ..]) -> any Call method in class
# (java "cls" "fld" ['any]) -> any Value of class field
# (java T "cls" ["cls" ..]) -> obj Define interface
# (java 'obj) -> [lst ..] Reflect object
# (java "cls") -> cls Get class
What does this tell us. The types of the arguments to java
specify the type of the call. These types may be
- symbols (transient, internal or external),
- short numbers,
- or
T
.
(read here for data types of PicoLisp).
Then a transient symbol cls
denotes a Java class name, and fld
a field in a class:
- An external symbol
obj
denotes a Java object - An internal symbol
msg
denotes a message (method call)
The following any
arguments may be of any type (numbers, symbols (normally strings) or lists (e. g., arrays in Java).
Now let's "translate" the line of code which checks the camera permissions:
getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
hasSystemFeature
is a method of the Android PackageManager abstract class and getPackageManager
is a method of the abstract Context
class. So this means, the line above is actually a short form of:
context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)
Now in PicoLisp: we can express context.getPackageManager()
by
(let PM (java CONTEXT 'getPackageManager))
where CONTEXT
is a pre-defined database object describing the android context (more on this later) and 'getPackageManager
is the message. This corresponds to the syntax:
# (java 'obj ['n] 'msg ['any ..]) -> any Send message to object
Then we call thehasSystemFeature
method of the package manager class:
(java PM 'hasSystemFeature "android.hardware.camera")
Now let's look at the takePictureIntent
in Java and PicoLisp:
# Java
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
# PicoLisp
(let Intent (java "android.content.Intent" T "android.media.action.IMAGE_CAPTURE"))
Next, the Java code
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
can be mapped to the PicoLisp function startActivityForResult
which is defined in lib/android.l
. Note that the intent is already defined within in the PicoLisp function startActivityForResult
:
(de startActivityForResult (Fun Action . @)
(let Intent (java "android.content.Intent" T Action)
(catch '("ActivityNotFound")
...
where Fun
is the function that should be handled and Action
is the activity, in this case "android.media.action.IMAGE_CAPTURE"
. It generates a new object by calling the new()
function in Java.
The complete example:
# Java (try-catch removed)
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(takePictureIntent, 1);
# PicoLisp
(startActivityForResult Fun "android.media.action.IMAGE_CAPTURE")
where Fun
defines what to do with the picture. We will see an example of this in the next post, where the picture will be saved in a temporary directory.
The CONTEXT object
In the example above, we used the CONTEXT
object in (java CONTEXT 'getPackageManager)
without explaining what is actually is. So let's do that now: CONTEXT
is defined in lib/android.l
as
# Android Context
(local) (CONTEXT GUI Config javaExt)
(de CONTEXT . {H@@@40000000000}) # file 32768, obj (hex "100000000")
where {H@@@40000000000}
is the external database object. We can inspect it in the PilBox Repl or from your local pty-connection:
android: (show CONTEXT)
{H@@@40000000000} ({H@@@1404477010} . "de.software_lab.pilbox.PicoLisp")
GUI {H@@@1725467726}
Home "/data/user/0/de.software_lab.pilbox/files"
Pid {H@@@27043500604}
Port "8081"
<...>
Uuid "de1958ea-d909-44a7-83f7-cd4d750a68ca"
Loaded T
-> {H@@@40000000000}
As you can see, the object's class is "de.software_lab.pilbox.PicoLisp"
. Let's check that one:
android: (show '{H@@@1404477010})
{H@@@1404477010} ({H@@@1114542263} . "android.app.Service")
NIL
mActivityManager
mApplication
<...>
-> {H@@@1404477010}
and we find that the superclass is "android.app.Service"
which shows that PicoLisp is actually running as a service. Apps have activities and services - the PilBox activity can be inspected in the GUI
property of CONTEXT
:
android: (show (get CONTEXT 'GUI))
{H@@@1725467726} ({H@@@1233152210} . "de.software_lab.pilbox.PilBoxActivity")
PilView {H@@@46020644}
<...>
Config {H@@@1565731746}
<...>
Home "/data/user/0/de.software_lab.pilbox/files/"
<...>
-> {H@@@1725467726}
From here, you could check the Config
component with (show (get CONTEXT 'GUI 'Config)
or PilView
with (show (get CONTEXT 'GUI 'PilView))
, and so on.
This post showed a quick (and rather superficial) introduction to how to map Java and PicoLisp code in order to access smartphone components. In the next posts, we will study some examples from the demo applications.