Functional Programming on Android

I’ve integrated CAL with a variety of Java based frameworks/environments: Tapestry, GWT and Google AppEngine for instance.

Recently someone asked on the CAL Google Group whether CAL programs could run on Android mobile devices. Android doesn’t use a standard JVM, it uses Dalvik, and it provides a subset of the normal JRE library.

I have put together a trivial proof of concept to show that CAL does indeed work on Android — although it doesn’t cover a great deal of CAL’s runtime behaviour, so there might be problems with larger programs which I haven’t detected.

My sample application just accumulates keypresses and displays the resulting string.

The Android Activity

This class delegates calls to onCreate and onKeyUp to CAL functions.

The state of the application (a String in this case) is initialised to the value returned by the CAL onCreate function, and is then passed to and returned from the onKeyUp function.

public class CalActivity extends Activity {
    private ExecutionContext ec;
    private TextView tv;
    private String state = null;

    public CalActivity() {
        System.setProperty("org.openquark.cal.machine.lecc.non_interruptible", "true");
        ec = StandaloneRuntime.makeExecutionContext(CalActivity.class);
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        tv = new TextView(this);
        try {
            state = Test.onCreate(CAL_Opaque.make(this), CAL_Opaque.make(savedInstanceState), ec);
            tv.setText(state.toString());
        } catch (CALExecutorException e) {
            tv.setText(e.getMessage());
        }
        setContentView(tv);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        char c = (char)event.getUnicodeChar();
        // ignore events which aren't characters
        if (c != 0) {
            try {
                state = Test.onKeyUp(CAL_Opaque.make(this), c, state, ec);
            } catch (CALExecutorException e) {
                tv.setText(e.getMessage());
            }
            tv.setText(state.toString());
            return true;
        }
        return super.onKeyUp(keyCode, event);
    }
}

The CAL Module

module Org.Kablambda.Android.Test;

... imports omitted ...

onCreate :: Activity -> Bundle -> String;
public onCreate activity bundle = "Hello from CAL!\n";

onKeyUp :: Activity -> Char -> String -> String;
public onKeyUp activity ch s = s ++ (fromChar ch);

Activity and Bundle are declared as foreign types, but don’t have any functions defined on them at present.

Modifications to the build process

I added some new targets to the standard build.xml file created by the android SDK.

<property name="quark.dir" location="${user.home}/dev/tools/cal-1.7.1" />
<property name="moduleName" value="Org.Kablambda.Android.Test"/>
<property name="className" value="org.kablambda.android.Test"/>

These properties set the location of our CAL installation, the name of the CAL module we want to call from Java, and the name of the class we want that module’s functions to be exposed in.

The copy-quark-jars target copies the jar files needed by CAL at runtime. They are copied into a temporary directory.

<target name="copy-quark-jars">
    <mkdir dir="tmp"/>
    <copy
            todir="tmp"
            flatten="true">
          <fileset dir="${quark.dir}/Quark/bin/java/release">
            <include name="**/calLibraries.jar" />
            <include name="**/calRuntime.jar" />
            <include name="**/calUtilities.jar" />
          </fileset>
          <fileset dir="${quark.dir}/Quark/lib/Resources/External/java/">
            <include name="**/icu4j.jar" />
            <include name="**/log4j.jar" />
          </fileset>
        </copy>
  </target>

The build-quark-standalone target compiles the CAL workspace workspace.cws into a jar file. This workspace must contain the module specified above and its dependencies. This target assumes that ${quarkdir}/Quark is on the PATH.

  <target name="build-quark-standalone" depends="copy-quark-jars">
    <exec executable="quarkc.sh">
        <arg value="workspace.cws"/>
        <arg value="-lib"/>
        <arg value="${moduleName}"/>
        <arg value="public"/>
        <arg value="${className}"/>
        <arg value="tmp/calmodule.jar"/>
        <arg value="-src"/>
        <arg value="calmodule-src.zip"/>
        <env key="QUARK_CP" value="src:bin:${sdk-location}/platforms/android-1.5/android.jar"/>
    </exec>
  </target>

Because several of the CAL jars contain duplicate files, which Android doesn’t tolerate, we merge all our jars into a single file, ignoring duplicates.

<target name="merge-jars" depends="build-quark-standalone">
    <zip destfile="libs/cal.jar" duplicate="preserve">
        <zipgroupfileset dir="tmp" includes="*.jar"/>           
    </zip>
</target>

To deploy your application to the Android Emulator you need to run ant merge-jars reinstall.

Note that during the process of converting the cal.jar file to Dalvik bytecode you’ll see a large number of error messages like this:

[apply] ...while processing com/ibm/icu/util/TimeZoneData.class
[apply] warning: Ignoring InnerClasses attribute for an anonymous inner class that doesn't come with an associated EnclosingMethod attribute. (This class was probably produced by a broken compiler.)

This is something to do with the rather old version of IBM’s International Components for Unicode library — it would be interesting to recompile CAL with a more recent version — or just recompiling that version of icu4j with the Sun compiler.

Further Work

The Android API needs to be exposed to CAL, and a Monad written to sequence Android API operations which have side effects. The CalActivity implementation should store the state as an opaque CalValue, so that implementations can choose what type to use for their state, and of course it needs to delegate all the possible events to CAl functions. There’s plenty of scope for more scaffolding on the CAL side.

As I don’t have an Android phone I doubt that I’ll do any further work on this — unless Dalvik gets an iPhone port :-)

2 Comments

  1. Posted July 28, 2009 at 2:13 pm | Permalink

    Nice work! This is making me think it’s almost worth it to get an Android phone.

  2. Posted August 13, 2010 at 11:05 am | Permalink

    So, that explains how Android functions, although its just for some keypresses and displays but it does explains a lot. Thanks for this post, very informative, Highly recommended.

Post a Comment

Your email is never shared. Required fields are marked *

*
*