RSS

Search Engine

Friday, September 10, 2010

Scripting Your Android Device

One of the issues that arose when Apple released the iPhone SDK earlier this year was the restriction on language interpreters:

No interpreted code may be downloaded and used in an Application except for code that is interpreted and run by Apple’s Published APIs and builtin interpreter(s)…An Application may not itself install or launch other executable code by any means, including without limitation through the use of a plug-in architecture, calling other frameworks, other APIs or otherwise.

Presumably, these terms are in place for security reasons — Apple and AT&T may be worried that un-vetted code might corrupt phones, consume large amounts of bandwidth, etc.

To date, Android has no such restriction, and if it will indeed be released as open source, it may be difficult for it to ever have such a restriction. Moreover, the Dalvik VM can be used with Java scripting languages. Today, we’ll take a peek at one such scripting language, discuss what languages may or may not work, and review the ramifications of scripting on the device.

My all-time favorite Java scripting language is Beanshell. To some extent, Beanshell was Groovy before Groovy. It gives you Java-compatible syntax with implicit typing and no compilation required. And, it isn’t all that big of a JAR to include — 143KB for the core.

To use Beanshell with Android, download the bsh-core.jar file, put it in a /lib folder in your project, and adjust your Eclipse settings or Ant script to reference that JAR during compilation and packaging. Note: I haven’t tried any of the add-on JARs for Beanshell — more on this below.

For example, here is a snippet of an Ant script that shows compiling a project and incorporating the Beanshell JAR in the build:


  1. <target name="compile" depends="dirs, resource-src, aidl">
  2. <javac encoding="ascii" target="1.5" debug="true" extdirs=""
  3. srcdir="."
  4. destdir="${outdir-classes}"
  5. bootclasspath="${android-jar}">
  6. <classpath>
  7. <fileset dir="lib">
  8. <include name="**/*.jar"/>
  9. fileset>
  10. classpath>
  11. javac>
  12. target>
  13. <target name="dex" depends="compile">
  14. <exec executable="${dx}" failonerror="true">
  15. <arg value="-JXmx384M" />
  16. <arg value="--dex" />
  17. <arg value="--output=${basedir}/${intermediate-dex}" />
  18. <arg value="--locals=full" />
  19. <arg value="--positions=lines" />
  20. <arg path="${basedir}/${outdir-classes}" />
  21. <arg path="${basedir}/lib/bsh-core-2.0b4.jar"/>
  22. exec>
  23. target>

From there, using Beanshell on Android is no different than using Beanshell in any other Java environment:

  1. Create an instance of the Beanshell Interpreter class

  2. Set any “globals” for the script’s use via Interpreter#set()

  3. Call Interpreter#eval() to run the script and, optionally, get the result of the last statement

For example, here is the XML layout for the world’s smallest Beanshell IDE:

  1. xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. >
  7. <Button
  8. android:id="@+id/eval"
  9. android:layout_width="fill_parent"
  10. android:layout_height="wrap_content"
  11. android:text="Go!"
  12. />
  13. <EditText
  14. android:id="@+id/script"
  15. android:layout_width="fill_parent"
  16. android:layout_height="fill_parent"
  17. android:singleLine="false"
  18. android:gravity="top"
  19. />
  20. LinearLayout>

Couple that with the following activity implementation:

  1. package com.commonsware.android.andshell;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Button;
  6. import android.widget.EditText;
  7. import android.widget.Toast;
  8. import bsh.Interpreter;
  9. public class MainActivity extends Activity {
  10. private Interpreter i=new Interpreter();
  11. @Override
  12. public void onCreate(Bundle icicle) {
  13. super.onCreate(icicle);
  14. setContentView(R.layout.main);
  15. Button btn=(Button)findViewById(R.id.eval);
  16. final EditText script=(EditText)findViewById(R.id.script);
  17. btn.setOnClickListener(new View.OnClickListener() {
  18. public void onClick(View view) {
  19. String src=script.getText().toString();
  20. try {
  21. i.set("context", MainActivity.this);
  22. i.eval(src);
  23. }
  24. catch (bsh.EvalError e) {
  25. Toast.makeText(MainActivity.this, e.toString(),
  26. 2000).show();
  27. }
  28. }
  29. });
  30. }
  31. }

Compile and run it (after replacing the aforementioned Ant steps as mentioned with the first source listing in this post). You’ll get the IDE:

Android Beanshell \

In the editor, enter the following code:

  1. import android.widget.Toast;
  2. Toast.makeText(context, "Hello, world!", 5000).show();

Note the use of context to refer to the activity when making the Toast. That is the global set by the activity to reference back to itself. You could call this global variable anything you want, so long as the set() call and the script code use the same name.

Then, click the Go! button, and you get:

Beanshell Executing

First, not all scripting languages will work. For example, those that implement their own form of just-in-time (JIT) compilation, generating Java bytecodes on the fly, would probably have to be augmented to generate Dalvik VM bytecodes instead of those for stock Java implementations. Simpler languages that execute off of parsed scripts, calling Java reflection APIs to call back into compiled classes, will likely work better. Even there, though, not every feature of the language may work, if it relies upon some facility in a traditional Java API that does not exist in Dalvik — for example, there could be stuff hidden inside Beanshell or the add-on JARs that does not work on today’s Android.

Second, scripting languages without JIT will inevitably be slower than compiled Dalvik applications. Slower may mean users experience sluggishness. Slower definitely means more battery life is consumed for the same amount of work. So, building a whole Android application in Beanshell, simply because you feel it is easier to program in, may cause your users to be unhappy.

Third, scripting languages that expose the whole Java API, like Beanshell, can pretty much do anything the underlying Android security model allows. So, if your application has the READ_CONTACTS permission, I would expect any Beanshell scripts your application runs to have the same permission.

This gets to the heart of Apple’s concern. It is one thing for a user to place some amount of blind trust in an application she may have purchased through an “app store”. But if that application in turn can be extended via scripting, now there’s a whole new level of trust that is implied — can the extensions themselves be trusted? — that the user may be unaware of.

To quote a random superhero movie, “with great power comes great responsibility” (and, apparently, a series of sequels, but that’s beside the point…).

If you are going to use a scripting language in your Android application — or any application, for that matter — it is your responsibility to take reasonable steps to ensure that griefers, crooks, and other no-good-niks do not harm your users. If you don’t, then we may be stuck with the Apple-style paternalistic model, which at best damages the open source vision of Android, and perhaps destroys it outright. We as a developer community must police ourselves if we do not want others to police us. Perhaps, over time, we can work out a scripting “code of conduct” we can all agree to.

On the flip side, this means that an Android version of GreasePocket might actually be “street legal”.

0 comments:

Post a Comment