Saturday, May 1, 2010

DroidZine Comic

DROIDZINE_BEGIN
page(
    title("PMOCT: R1 P9"),
    author("~annarowlye"),
    image(
"http://fc08.deviantart.net/fs70/f/2010/120/8/f/PMOCT__R1_P9_by_annarowlye.jpg"),
    panel1(
        box(0, 0, 292, 278),
        moves(right(2), down(4))),
    panel2(
        box(292, 0, 252, 279),
        moves(left(1), right(3), down(4))),
    panel3(
        box(538, 0, 262, 271),
        moves(left(2), right(4), down(4))),
    panel4(
        box(0, 281, 408, 443),
        moves(left(3), right(5), down(6))),
    panel5(
        box(406, 282, 396, 439),
        moves(left(4), right(6), down(7))),
    panel6(
        box(4, 725, 390, 496),
        moves(left(5), right(7), up(4))),
    panel7(
        box(404, 731, 396, 489), 
        moves(left(6), up(5))))
DROIDZINE_END

Tuesday, April 27, 2010

A self generating Android program

This is a more or less insane DroidScript program I prepared as a demo for a seminar on dynamic languages. The program generates a main activity with a list from which you can launch 10000 generated activities. The theme I had in mind was "Fortune Cards", but all I had time to do was a coloured oval. If I get some more time I will improve the program.

I recommend that you try with a smaller number if you want to run the program, say 100. I wanted to show that it is possible to create a really big program in JavaScript on Android in terms of the size of the scrolling list and the number of activities, and still get good performance.

var DroidScriptFileHandler = Packages.comikit.droidscript.DroidScriptFileHandler;
var Intent = Packages.android.content.Intent;

function createAppScript()
{
    return """function onCreate(bundle)
    {
        var lang = Packages.java.lang;
        var android = Packages.android;
        var widget = Packages.android.widget;

        var listView = new widget.ListView(Activity);
        
        var numberOfCards = 10000;
        var cards = lang.reflect.Array.newInstance(lang.String, numberOfCards);
        for (var i = 0; i < numberOfCards; ++i)
        {
            cards[i] = "Card " + (i + 1);
        }

        var arrayAdapter =
            new widget.ArrayAdapter(Activity,
               android.R.layout.simple_list_item_1,
               cards);

        listView.setAdapter(arrayAdapter);
        
        listView.setOnItemClickListener(function(parent, view, position, id) {
            var Intent = Packages.android.content.Intent;
            var DroidScriptFileHandler = Packages.comikit.droidscript.DroidScriptFileHandler;
            var script = DroidScriptFileHandler.create().readStringFromFileOrUrl(
                "droidscript/generated/Card" + position + ".js");
            var intent = new Intent();
            intent.setClassName(Activity, "comikit.droidscript.DroidScriptActivity");
            intent.putExtra("Script", script);
            Activity.startActivity(intent);
        });

        Activity.setContentView(listView);
    }
    """;
}

function createCardScript()
{
    function random255() { return Math.random() * 255; }
        
    var script = """function onCreate(bundle)
    {
        var lang = Packages.java.lang;
        var android = Packages.android;
        var widget = Packages.android.widget;
        var Morph = Packages.comikit.droidscript.Morph;
        var Paint = Packages.android.graphics.Paint;
        var Color = Packages.android.graphics.Color;
        var RectF = Packages.android.graphics.RectF;
                
        var morph = new Morph(Activity);
        var width = 100;
        var height = 100;
        
        morph.setOnDrawListener(function(canvas)
        {""";
     
     var red = random255();
     var green = random255();
     var blue = random255();
     
     script += """
         var brushColor = Color.rgb(""" 
            + red + ", " + green + ", " + blue + """);
         var paint = new Paint();
         paint.setColor(brushColor);
         paint.setStyle(Paint.Style.FILL);
         paint.setAntiAlias(true);
         canvas.drawOval(
             new RectF(
                 0,
                 0,
                 width,
                 height),
                 paint);""";
                
     script += """});
        
        morph.setOnSizeChangedListener(function(w, h, oldw, oldh)
        {
            width = w;
            height = h;
            morph.invalidate();
        });
        
        Activity.setContentView(morph);
    }""";
    
    return script;
}

function launch(script)
{
    var intent = new Intent();
    intent.setClassName(Activity, "comikit.droidscript.DroidScriptActivity");
    intent.putExtra("Script", script);
    Activity.startActivity(intent);
}

// Create directory
var directory = "droidscript/generated/";
var fileHandler = DroidScriptFileHandler.create();
fileHandler.externalStorageCreateDirectory("droidscript/generated/");
    
// Generate main script
fileHandler.writeStringToFile(directory + "CardApp.js", createAppScript());

// Generate card scripts
for (var i = 0; i < 10000; ++i)
{
    fileHandler.writeStringToFile(
        directory + "Card" + i + ".js", createCardScript());
}

// I suggest you first run the above code in the editor at droidscript.se, 
// then launch the app. And I strongly recommend to try a smaller number 
// of "cards", say 100.

// Launch app
launch(DroidScriptFileHandler.create().readStringFromFileOrUrl(
    "droidscript/generated/CardApp.js"));

Friday, April 23, 2010

Deploying DroidScript Apps

Have made a first test on how to deploy a DroidScript program as a "native" Android application. The app is a demo that illustrates how comic strip formats on Android. Download info can be found at: http://comikit.se

Will get back with instructions on how the app is built. All files, both JavaScript files and images are included in the app as assets. A tiny Java activity starts the main script.

Sunday, April 11, 2010

Multiline strings and using DroidScript with WebKit

After a walk in the sun, I have uploaded yet a new DroidScript version, have made various enhancements, including multiline strings. JavaScript does not have multiline strings, so I have added that to DroidScript. The syntax is similar to Python, with three quotes to begin and terminate a multiline string.

Below is an example that uses this feature. The example shows how to make a DroidScript app that opens a WebView (a WebKit browser) and displays an HTML document encoded as a multiline string. The example also shows how to use addJavascriptInterface to import Java objects into the WebView, and how to make calls from JavaScript in the HTML document to DroidScript. The JavaScript in the HTML document is executed by the WebKit JavaScript engine, and does not have direct access to the Android class library. The JavaScript code that gets evaluated by DroidScript executes on Rhino, and can therefore use the Android API directly.

The short link to this script is: http://bit.ly/bL1heJ
DROIDSCRIPT_BEGIN
function onCreate(bubble)
{
    var WebView = Packages.android.webkit.WebView;

    var webview = new WebView(Activity);
    webview.getSettings().setJavaScriptEnabled(true);
    webview.addJavascriptInterface(Activity, "activity");
    Activity.setContentView(webview);    
    
    var content =
    """<html>
        <body>
            <script>
            function showToast(message) {
                activity.eval(
                    "var Toast = Packages.android.widget.Toast;" +
                    "Toast.makeText(Activity, '" + message + "', " +
                    "Toast.LENGTH_SHORT).show();"); }
            </script>
            <h1>Take the pill</h1>
            <input 
                type="button" 
                value="Take pill" 
                onclick="showToast('You have taken the red pill!')">
        </body>
    </html>""";
    
    webview.loadData(content, "text/html", "utf-8");
}
DROIDSCRIPT_END

Now it is easy to distribute DroidScript scripts

I have uploaded a new version of DroidScript that has a feature for easily distributing DroidScript scripts. With a new pair of tags, you can publish scripts on any web page (like this blog) and open and run them from the DroidScript app on your Android device.

It is as easy as opening a web page, and that said, this is also an easy way of distributing malicious code.

BE VERY CAREFUL WITH WHAT YOU DOWNLOAD!

I will say this again, because it is important:

BE VERY CAREFUL WITH WHAT YOU DOWNLOAD!

At present, DroidScript has the following permissions:

android.permission.INTERNET
android.permission.WRITE_EXTERNAL_STORAGE
android.permission.CAMERA
android.permission.ACCESS_FINE_LOCATION

So a script could erase your files, mess with your camera, and so on.

That said, I think this is a really cool feature, and it shows how powerful dynamic languages are when it comes to ease of distributing and running programs. And with improved security handling, the risks of distributing scripts could be reduced.

I do like the openness of the web, and native apps are sadly not open for inspection and modification by the user. With DroidScript, the script is always available for preview and modification in the editor on the device before you decide to run it. Still, it is a big security risk to be open, but so much more fun. It is like life, a life without risk taking is no life.

DroidScript is an ongoing experiment, and it is possible to package a program in alternative ways. You could, for instance, embed DroidScript in a native Android app, store JavaScript files in the application space (as resources or files), and no one would be able to tamper with the scripts or download new scripts, and the user would not even notice that the app is written in JavaScript.

Now for an example:
DROIDSCRIPT_BEGIN
function onCreate(bubble)
{
    var Button = Packages.android.widget.Button;
    var Color = Packages.android.graphics.Color;
    var Typeface = Packages.android.graphics.Typeface;

    var numberOfClicks = 0;
    var font = Typeface.create(Typeface.SANS_SERIF, Typeface.BOLD);
    var button = new Button(Activity);
    button.setText("Hello World!");
    button.setTypeface(font);
    button.setTextSize(26);
    button.setBackgroundColor(Color.rgb(0, 0, 64));
    button.setTextColor(Color.rgb(255, 255, 255));
    button.setOnClickListener(function () {
        ++numberOfClicks;
        button.setText(
            "You Clicked Me" +
            (1 == numberOfClicks ? "!" : 
                " " + numberOfClicks + " Times!")); })
    Activity.setContentView(button);
}
DROIDSCRIPT_END
How to run the above program:
  • Download the latest DroidScript app (instructions are found here
  • Start the app and select "Open script" in the options menu
  • Enter the short url to this page: http://bit.ly/9kFqEi
  • Then press "Run Activity"
(The short url is quicker to type on the device)

DroidScript will find the code between the tags and filter out the rest of the web page. This makes it very easy to publish scripts embedded on blogs and other web pages. You can of course also put your script in a plain text file and upload that to the web, then you will not need the begin and end tags.

Since a script can open images over the internet, and open other scripts, it is possible to create truly dynamic applications that have the capabilities of native Android applications and the dynamic flow and instant access of web pages.

Android CameraPreview sample in JavaScript

Here is a DroidScript program that displays preview images from the camera. I have taken the CameraPreview sample program and rewritten it in JavaScript.

Beware! This program runs fine on Motorola Droid :-) but crashes on Nexus One :-(

Update: @JonasBeckman hinted that uncommenting camera.setParameters(parameters); when running on Nexus One does the trick. I tested and it works fine! :-)

To run this program, open the DroidScript app, and select the "Open script" menu item, and enter the short url to this blog post: http://bit.ly/aB2fik

DroidScript will recognise the DROIDSCRIPT_BEGIN and DROIDSCRIPT_END tags and extract the code between the tags. Then press the "Run Activity" button to run the program.

Note that these tags can occur only once on a page. If there are multiple occurrences, the text between the first pair will be used. Therefore, I have escaped the underscore character in the tags above, so that they will not be interpreted as containing DroidScript code.

DROIDSCRIPT_BEGIN
var Camera = Packages.android.hardware.Camera;
var SurfaceHolder = Packages.android.view.SurfaceHolder;
var SurfaceView = Packages.android.view.SurfaceView;
var Window = Packages.android.view.Window;

function onCreate(bundle)
{
    Activity.requestWindowFeature(Window.FEATURE_NO_TITLE);
    var preview = createPreviewSurface();
    Activity.setContentView(preview.getSurfaceView());
}

function createPreviewSurface()
{
    var camera = null;
    var surface = new SurfaceView(Activity);
    
    var object = {
        
        getSurfaceView : function() {
            return surface; },
            
        surfaceCreated : function(holder) {
            camera = Camera.open();
            try {
                camera.setPreviewDisplay(holder); }
            catch (exception) {
                camera.release();
                camera = null; } },
                
        surfaceDestroyed : function(holder) {
            camera.stopPreview();
            camera.release();
            camera = null; },
            
        surfaceChanged : function(holder, format, w, h) {
            var parameters = camera.getParameters();
            parameters.setPreviewSize(w, h);
            // Causes camera to fail on Nexus One
            //camera.setParameters(parameters);
            camera.startPreview(); }
    };
 
    var callback = createInstance(
        Packages.android.view.SurfaceHolder.Callback, 
        object);
    surface.getHolder().addCallback(callback);
    surface.getHolder().setType(
        SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);

    return object;
}

// Create an instance of a Java interface.
//   javaInterface - the interface type
//   object - JS object that will receive messages
//     sent to the instance
function createInstance(javaInterface, object)
{
    // Convert a Java array to a JavaScript array
    function javaArrayToJsArray(javaArray)
    {
        var jsArray = [];
        for (var i = 0; i < javaArray.length; ++i) {
            jsArray[i] = javaArray[i];
        }
        return jsArray;
    }
    
    var lang = Packages.java.lang;
    var interfaces = 
        lang.reflect.Array.newInstance(lang.Class, 1);
    interfaces[0] = javaInterface;
    var obj = lang.reflect.Proxy.newProxyInstance(
        lang.ClassLoader.getSystemClassLoader(),
        interfaces,
        // Note, args is a Java array.
        function(proxy, method, args) {
            // Convert Java array to JavaScript array
            return object[method.getName()].apply(
                null,
                javaArrayToJsArray(args));
        });
    return obj;
}
DROIDSCRIPT_END

Monday, April 5, 2010

Version 4 of DroidScript

Uploaded version 4 of DroidScript today, including new example programs! (I use plain sequential version numbers; version 4 is very much in an early experimental stage!) Download info can be found here: http://droidscript.blogspot.com/2010/03/droidscript-getting-started.html

If you already have DroidScript installed, you need to manually update the JavaScript files on the SD-card. One method is to delete the JavaScript files in the droidscript folder on the SD-card; if they are missing when the app started, they will be downloaded automatically. An easier method is to open DroidScript, press the menu button on the device, and select "Update". That will download and overwrite the JavaScript files in the droidscript folder with the latest versions. Then restart DroidScript.

This whole process can of course be improved. One idea is to make DroidScript check the update server for a new version number, and update the JavaScript code if needed. Admittedly, it is a bit of a hazzle to keep track of updating the actual app and the JavaScript files on the SD-card. But there is also a reason for making manual updates; the user might have modified the JavaScript code, and may wish to save the modifications before updating.

The motivation for putting JavaScript files on the SD-card is highest possible transparency. A user always has access to the content of the card, and can easily inspect and edit the JavaScript code if she wishes. This way, DroidScript is much more malleable and exploration-friendly, compared to putting the code hidden away in the application's private space. The drawback is that files might be messed up, and if you swap SD-cards the files are not there anymore (buy then they will be dynamically downloaded).

New in this version is improved error handling, a new view class for writing graphical applications, and more examples programs.

Errors are now trapped in a more robust way, and the application does no longer shut down in case of runtime JavaScript errors. The notification bar is used to display JavaScript errors. It is a big advantage that notifications are decoupled from the application, so if the application becomes faulty, it is still possible to retrieve the error message form the notification bar. Errors are also logged to the logcat output.

A problem with scripting Java from JavaScript on Android is that there is no support for generating Dalvik byte code, and this is needed for subclassing. This means that subclassing of Java classes in JavaScript is not possible. Since the Android API relies heavily on subclassing in certain areas, this sort of spoils the party. Subclassing View to create your own view class with custom drawing, is impossible, for example. However, a nice solution is to make a subclass of View in Java that has pluggable listeners for drawing and other events that normally would be handled through subclassing. This way, we also get a much nice design that allows for runtime configuration of for example the drawing behaviour (subclassing is one nasty compile-time bound relation that I would be happy to avoid and replace by delegation).

The new DroidScript version includes a Java class called Morph that has pluggable listeners for drawing and interaction behaviours, which makes it possible to write JavaScript apps with custom graphical rendering, such as games and painting programs. The name "Morph" means "Shape" and is taken from Self/Squeak; but the DroidScript Morph is nowhere nearly as powerful as a Squeak morph, but it might be one day :-)

There are two new example programs included with the version of DroidScript, a program that displays random colours when the screen is pressed, and a painting program. The speed of the painting program shows that JavaScript applications can run at satisfactory speed on Android (at least on a Nexus One).

Here is the source code of the example programs:
http://github.com/divineprog/droidscript/blob/master/javascript/Colors.js
http://github.com/divineprog/droidscript/blob/master/javascript/Paint.js

The example programs are included in the latest version of DroidScript, so you can run them directly on the device, and you can also copy and paste them into the DroidScript Live Editor and run them from there.