All Manuals > LispWorks User Guide and Reference Manual > 16 Android interface

NextPrevUpTopContentsIndex

16.3 The Othello demo for Android

The Othello demo is a simple Android app showing the basics of using LispWorks for Android Runtime. It is a full Android project that can be imported into an Android development environment, for example Eclipse with ADT and Android Studio.

The application plays the Othello game as an example of an application. When delivering "with Lisp" (see Delivering LispWorks to the project below), it also allows the user to type and evaluate Lisp forms. This is useful during development.

To try the demo, you need to do these steps:

  1. Create an Android project containing the demo Android code.
  2. Deliver the LispWorks application to it.
  3. Build and install the application and run it.

These steps are described in detail in the following sections.

16.3.1 Creating an Android project

The Android project code is in ./OthelloDemo/, which is the directory examples/android/OthelloDemo inside the LispWorks distribution. You need to make a project with this code.

The following sub-sections provide the detail for Eclipse and Android studio projects respectively.

16.3.1.1 Othello project in Eclipse (with ADT)

This procedure was checked with ADT build: v22.6.2:

  1. You need to have a workspace (corresponding to a directory), which may be completely empty.
  2. Choose File > Import from the menubar to raise the Import dialog.
  3. In the Import dialog expand Android , select Existing Android Code Into Workspace and click Next .
  4. You should be in the Import Projects tab. Click Browse... .
  5. Navigate to the examples/android/OthelloDemo directory inside the LispWorks distribution directory, select it and press Ok .
  6. Click Select All to ensure that the project is selected.
  7. Select the Copy projects into workspace check button (below the list).
  8. Click Finish .

The new project name defaults to "LispWorksRuntimeDemo", and this, relative to the workspace directory, is the project path that you will need to set the *project-path* to (below). You can change that before clicking Finish .

Note: if you get errors when trying to build, try:

  1. Check that the Project build target is 3.0 or higher. Do: Project > Preferences , click Android , and it shows the list of targets available. Select the highest.
  2. Try "cleaning": Project > Clean .
16.3.1.2 Othello project in Android studio

This procedure was checked with Android studio 0.4.6:

  1. Inside Android studio, select Import project... either from the Welcome to Android studio dialog or from the File menu. That raises the Select Gradle Project Import dialog.
  2. In the Select Gradle Project Import dialog, select the examples/android/OthelloDemo directory in the LispWorks distribution and press OK . That should raise a dialog called Import Project from ADT (Eclipse Android) .
  3. Select a directory to put the project in and press OK . This is the "project path" that you will need to set the *project-path* variable to (below).
  4. Leave all the default settings in the next page and press Finish .

16.3.2 Delivering LispWorks to the project

To deliver LispWorks, copy one of the build script files deliver-android-othello.lisp or deliver-android-othello-with-lisp.lisp from the examples/android directory in the LispWorks distribution. In the copied file change the value of the variable *project-path* to point to the directory of the project that you created above. For example:

(defvar *project-path* "~/my-workspace/LispWorksRuntimeDemo/")

You will then use your edited copy of the build script as the -build command line argument to LispWorks.

The Android delivery image is called lispworks-7-1-0-arm-linux-android. This must be run on an ARM architecture, currently that means ARM Linux or an emulator. To run this image under the QEMU emulator, use the script examples/android/run-lw-android.sh.

run-lw-android.sh -build /path/to/deliver-android-othello.lisp

See deliver-to-android-project for details.

If you cannot access the directories that you use in your project from the ARM machine:

  1. Change *project-path* to some temporary directory, and add :no-sub-dir t to the call to deliver-to-android-project. For example, if you use deliver-android-othello.lisp, the call should be:
  2. (deliver-to-android-project nil *project-path* 5 :no-sub-dir t)

    This will create two files in the temporary directory called libLispWorks.so and LispWorks.so.lwheap.

  3. Copy libLispWorks.so to the directory libs/armeabi-v7a inside your project, and copy LispWorks.so.lwheap to the assets directory inside your project (assuming you are using Eclipse/ADT).

The two deliver scripts, deliver-android-othello.lisp ("without Lisp" ) and deliver-android-othello-with-lisp.lisp ("with Lisp") differ in what the application contains. The "with Lisp" script keeps a lot of Lisp in the application, and hence allows evaluation of Lisp forms when running on Android. The "without Lisp" script delivers at the maximum delivery level, and hence cannot evaluate forms. As a result the runtime image is much smaller.

16.3.3 Running the application

Once you have the project with the LispWorks files, you can build, install it on the device and run it as any other Android project. When it runs, It first shows a splash screen (the LispWorks splash screen image) and then the first screen displays an Othello board, where you can play against the computer (you play black), by touching the square where you want to add your piece.

The display has two elements in addition to the board:

It also has a menu (which maybe partly displayed on the action bar), with these items:

Restart

Restart the game.

Undo

Undo the last move. You can undo repeatedly to the beginning of the game.

When the computer plays, each undo undoes to the state before your last move.

When the computer does not play, it undoes one move.

When delivering "with Lisp" the menu also has these items:

Lisp Panel

Takes you to the Lisp Panel screen, which allows you to evaluate Lisp forms. See below in the description of the Lisp Panel.

Command history

Takes you a list of the forms that that you evaluated. It is initialized by a few demo forms. See below about the History list.

Othello Server

Raises a submenu with three items: Java server , full proxy and lazy proxy . Switching between these changes the mechanism by which Java calls into Lisp. The behavior of the game is exactly the same, only the output to the Lisp Panel or Output is different. This feature is for demonstrating different techniques of calling from Java to Lisp. See discussion of the code for details.

When delivering "without Lisp" the menu also has these items:

Output

Takes you to the "output" screen.

16.3.3.1 The Lisp Panel screen

The Lisp Panel contains a row of buttons, a text view for input, and the bottom is a text view for output. This screen is available only when delivering "with Lisp". When delivering "without Lisp", there is the Output screen instead.

The buttons are:

Clear

Clears all the output from the output pane.

Evaluate the string

Send the current text in the input pane to Lisp by a direct call to eval-for-android.

eval-for-android is defined in (example-edit-file "android/android-othello-user"). It reads the string and evaluates it. If it is successful, it prints to the output pane the form, anything the form printed, and the result(s). If there is an error, it logs the error and prints the error message to the output pane.

History

Takes you to another screen which displays a list of the forms that were evaluated. The list is initialized by some forms which demonstrate some features of the multiprocessing on Android. See below in the section Prepared forms. Whenever you evaluate a form by pressing Evaluate the string , it adds the form to the history in the beginning of it. If the form matches exactly a form which is already in the list, the old item is removed.

In the history list, when you touch an item it is inserted into the input pane, and the application switches to the Lisp Panel. It does not evaluate the form at that point.

You can also reach the history list from the menu in the Othello screen.

Bug form logs

Invokes com.lispworks.Manager.showBugFormLogs. This shows another screen with a list of the logged errors displaying the error string for each item. Touching an item opens another screen with bug form log of this error.

Clear logs

Clears all the bug form logs, including removing the files.

The input pane below the buttons is just a passive text view, in which you can type Lisp forms, and evaluate by touching the Evaluate the string button.

The bottom part of the Lisp Panel, in the Output screen when delivering "without Lisp", is the output pane. It prints the output of evaluation as above. It also prints whenever you touch a square in the Othello board. When the Full or Lazy proxy is used for communication, it also prints this fact.

16.3.3.2 Prepared forms

Initially, the History list contains the forms described below. When using forms, note that evaluating a form moves it to the top of the list. When you should evaluate more than one of these forms in order, you will need to look down the list for each one in turn.

The idea is that you can try these forms, and then modify them to check and perform things that you need to do when debugging your application.

Forms:

  1.  
  2. (mp:ps)

    Shows the Lisp processes. Initially there are at least the idle process and the GUI process which displays as "created by foreign code".

  3.  
  4. (setq *computer-plays-waste-time-in-seconds* 2)

    That causes the computer to pretend that it takes it time to compute a move. When playing against the computer after setting this, you will see that after your move, the display says "Computer to play" for two seconds before it actually plays. Set *computer-plays-waste-time-in-seconds* back to nil to make it behave normally.

  5.  
  6. (defun eval-and-print (form)
      (let ((res (eval form)))
        (lw-ji:send-message-to-java-host
         (princ-to-string res) :reset)))

    Defines a function to be used by the next two forms. Note that it uses send-message-to-java-host to print, which comes in the output and works on any thread. When it is on the current thread it will end up printing before the printing of the evaluation, but on another thread it is random which output comes first.

  7.  
  8. (eval-and-print '(mp:get-current-process))

    Use the function defined above to print the process in the current thread. That is the GUI process.

  9.  
  10. (mp:funcall-async 'eval-and-print '(mp:get-current-process))

    Use the function eval-and-print defined above to print the process on which funcall-async executes the function. This will be one of the Background Execute processes.

  11.  
  12. (progn 
      (defun loop-executing-events ()
        (loop
         (let ((event (mp:process-wait-for-event)))
           (lw-ji:format-to-java-host "~%got event ~s" event)
           (let ((res (mp:general-handle-event event)))
             (lw-ji:format-to-java-host
              "~%Handling got ~s" res)))))
      (setq loop-executing-events-process
            (mp:process-run-function "Loop Execute Events" ()
                                     'loop-executing-events))) 

    Create a process called "Loop Execute Events" and set loop-executing-events-process to it. The process has a process function loop-executing-events which read events and handles them using process-wait-for-event and format-to-java-host. It prints "got event <event>" and then "handling got <result of handling>". Note the usage of format-to-java-host, which prints to the output pane too (it actually calls send-message-to-java-host).

  13.  
  14. (mp:process-send loop-executing-events-process '(mp:get-current-process))

    Sends to the "Loop Execute Events" process (that started in the previous step) an event, which cause get-current-process to be called, and hence return the process. You should see "got event (MP:GET-CURRENT-PROCESS)" and "Handling got <process name>"

  15.  
  16. (othello-user-change-a-square 5 2)

    Changes square 5 (sixth from the left in the top row) to color 2 (black). This function is defined in (example-edit-file "android/android-othello-user") and is part of the "interface" that the Lisp Othello code uses to tell Java to change the board.

  17.  
  18. (mp:process-run-function
     "multiplier" ()
     #'(lambda()
         (setq *finish-multiply* nil)
         (dotimes (x 100)
           (sleep 1)
           (when *finish-multiply* (return))
           (lw-ji:format-to-java-host
            \"~%~d * ~d = ~d\"
            x x (* x x))))) 

    Starts a process that performs "a lengthy computation" (simulated by using (sleep 1)) and prints results while doing it. In each "step in the computation" (the cl:dotimes iteration) it prints the square of the iteration number. To stop it, evaluate the next form.

  19.  
  20. (setq *finish-multiply* t)

    Tell the "multiplier" process (see above) to stop.

  21.  
  22. (mp:process-run-function 
     "Error"
     () #'(lambda () (open "junk;;file::name")))

    Starts another process that gets an error (because the argument to cl:open is an illegal pathname). It prints that it got the error, and you can use the Bug form logs button to look at the bug form log.

  23.  
  24. (raise-alert-dialog
     "What do you want to eat?" +
     :ok-title "Chicken " 
     :ok-callback '(raise-alert-dialog "Here is some chicken") + 
     :cancel-title "Salad "
     :cancel-callback '(raise-alert-dialog "We do not have salad"))

    Raises an alert dialog using raise-alert-dialog which is defined in dialog.lisp. Note that this works because the LispPanel class uses com.lispworks.Manager.setCurrentActivity to set the current activity.

  25.  
  26. (raise-a-toast "Bla Bla Bla" :gravity :left)

    Raises an Android "toast" at the middle of the left side, using raise-a-toast which is defined in toast.lisp.

16.3.4 Lisp interface usage in the Java code

The Othello Demo Java code is in the package com.lispworks.example.othellodemo. LispWorks interfaces in Java are all in the package com.lispworks. The methods appear in full, to make it is easy to see where there is a call to the LispWorks interface.

16.3.4.1 Class Othello

Othello is a subclass of Activity that displays the screen with the Othello board. The display is all in standard Java. The board is made of a grid of 64 ImageView panes, each one displaying one of three images (blank, white, black). Each view has an OnClickListener(SquareListener) that remembers its index and passes it when clicked.

The Java code does not know anything about the game that is being played, and does not keep a record of the state of the game. That is all done in Lisp.

The Java code processes user gestures concerning the game (touching the board, and touching any of the buttons and items Computer plays , undo move , restart ) by calling methods on an object that implements the nested interface OthelloServer, which is kept in mOthelloServer. The object can be either a Lisp proxy, or of the nested class JavaOthelloServer. All of these objects do exactly the same thing (calling the Lisp functions defined in (example-edit-file "misc/othello")), and the purpose of having all these options is to demonstrate different techniques to call into Lisp. There is also a nested class ErrorOthelloServer in case LispWorks does not work, which displays the error. mOthelloServer is set by the method setupServer.

The nested class JavaOthelloServer is plain Java with methods that call into Lisp using the Direct calls interface (com.lispworks.LispCalls.callIntV and com.lispworks.LispCalls.callVoidV). This has the advantage that on the Lisp side all you have to do is to ensure that the functions are not shaken, which you can do with hcl:deliver-keep-symbols (see the LispWorks Delivery User Guide ). It has the disadvantage that you hardwire Lisp function names in Java (though the names can be variables too).

The other two possible implementations of the OthelloServer are Lisp proxies which are defined in Lisp (in examples/android/android-othello-user.lisp). See the discussion of the Lisp code for more details. The code in setupServer demonstrates two techniques of using the proxy definitions: either calling a Lisp function that makes a proxy (using com.lispworks.LispCalls.callObjectV to call create-lisp-othello-server), or using com.lispworks.LispCalls.createLispProxy with the name of the proxy definition (lisp-othello-server-lazy) to create a proxy.

To actually respond to moves, the Othello class exports 3 methods ("updateState", "signalBadMove" and "change") which are called directly from Lisp to change the board and the status text.

When an Othello instance is created, it calls setupAndInit to do anything with Lisp (mainly call mOthelloServer.init). Before doing anything that may interact with Lisp, it checks the status of Lisp using com.lispworks.Manager.status. If Lisp is not ready and there was no error, it calls com.lispworks.Manager.init to initialize LispWorks, passing it a Runnable that with call setupAndInit again to actually do the initialization. In the Demo the Lisp side will already be initialized, because it is done by the LispWorksRuntimeDemo activity, but the Othello class avoids relying on it.

When LispWorks is ready, setupAndInit sets up the server by calling setupServer and initializes the game by calling mOthelloServer.init.

If there is an error, setupAndInit gets the error details using com.lispworks.Manager.mInitErrorString, and com.lispworks.Manager.init_result_code and adds a message, set mOthelloServer to ErrorOthelloServer, and then shows the Lisp Panel which will be displaying the error.

There is also an onCreateOptionsMenu method which checks whether Lisp is working and can evaluate forms (using LispPanel.canEvaluate), and accordingly decides which menu to use.

16.3.4.2 Class LispPanel

LispPanel is a subclass of Activity that displays the Lisp panel, or just the output when delivering "without Lisp" (see Delivering LispWorks to the project).

The main purpose of the Lisp Panel is to evaluate Lisp forms, which it does by calling the Lisp function eval-for-android using com.lispworks.LispCalls.callIntV. That can work only if eval-for-android is defined, so LispPanel has a method canEvaluate that works by checking if eval-for-android is defined using com.lispworks.LispCalls.checkLispSymbol. If eval-for-android is fbound, LispPanel displays in full, otherwise it shows only output TextView.

LispPanel is also responsible for displaying messages in its output TextView. To achieve that, it uses com.lispworks.Manager.setTextView. Once it sets the TextView, all calls to com.lispworks.Manager.addMessage and calls to the Lisp functions send-message-to-java-host and format-to-java-host put their output in this TextView.

Other usage of the com.lispworks package in LispPanel are:

16.3.4.3 Class MyApplication

MyApplication is not actually used in the demo. It is a demonstration of how to initialize LispWorks when the application starts, by calling com.lispworks.Manager.init in the onCreate of the application. The demo itself does not use this mechanism. Instead the SplashScreen activity does it, and the Othello activity also checks using com.lispworks.Manager.status, and if LispWorks needs initializing does it.

16.3.4.4 Class LispWorksRuntimeDemo

Display a splash screen and initialize the Lisp side, by checking com.lispworks.Manager.status and using com.lispworks.Manager.init if needed. The purpose of this class is just to give an example of displaying a splash screen while initializing Lisp. It is not really needed, because the Othello class checks too (in setupAndInit). On Eclipse the name of this class is the default project name.

16.3.4.5 Class History

A simple class to display Lisp forms. Does not do anything related to Lisp.

16.3.4.6 Class SquareLayout

A simple class to make a square layout for displaying the Othello board. Does not do anything related to Lisp.

16.3.5 Java and Android interface in the Lisp code

The file

(example-edit-file "misc/othello")

is a generic implementation of the playing Othello part, and has nothing to do with Java or Android.

The Lisp code that interacts with Java and Android to play Othello and evaluate the forms is in

(example-edit-file "android/android-othello-user")

The Java callers to update the game are defined by a define-java-caller form. All these methods need to be called on the GUI thread (because they interact with GUI elements), so the actual functions that are called from the Othello code are defined to call the Java callers using android-funcall-in-main-thread.

The function eval-for-android is what the Java code uses to evaluate Lisp forms. The function has no Java-specific features, but it has error handling and binding of some of the top-level variables like cl:* to make it more usable in repeated calls from "outside".

The code also defines two proxy definitions that implement the Othello.OthelloServer interface which responds to user gestures. To demonstrate the various features of proxies, there are two definitions which achieve exactly the same thing. The full proxy definition (lisp-othello-server-full) specifies functions for all the methods that the interface defines. The lazy (programmer) proxy definition does not define any method. Instead it has a default function that decides what to do based on the method name.

The two files

(example-edit-file "android/dialog")

and

(example-edit-file "android/toast")

define the functions raise-alert-dialog and raise-a-toast respectively, to demonstrate using Android code directly from Lisp. See the comments in these files.

 


LispWorks User Guide and Reference Manual - 20 Sep 2017

NextPrevUpTopContentsIndex