Mobile App Development in PicoLisp - VI:  Intents and the Java Interface

Mobile App Development in PicoLisp - VI: Intents and the Java Interface

·

6 min read

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.

intents.png

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.


Sources