Thursday, December 22, 2011

NFC - NDEF Support

NDEF messages are the standard way to write information to NFC tags on Android.
The latest version of PAW (0.84) has support for text, URI, smart poster and mime media NDEF messages.
The classes and their methods are described in the PAW Functions documentation within the PAW web application.
NFC support is now also available in PAW Runtime.
To show how the new functionality can be used (and to give you the possibility to play with NFC tags), a NFC reader/writer plugin is available for PAW. This plugin is also available in the newest add-on package for PAW Runtime.

Here are some screenshots...

Plugin

NFC Plugin
Plugin requesting NFC Tag

Runtime

PAW Runtime with NFC Reader/Writer
Runtime version


This will be the last update of PAW this year and possibly the last blog entry.
To all of you ... Merry Christmas and a Happy New Year :)

Thursday, December 15, 2011

ICS: Getting NFC Tags to Work

Since December I'm a happy owner of a Galaxy Nexus :)
NFC seemed like an interesting thing, so I started to integrate NFC support into PAW.

First thing I did was to order some Mifare Classic 1K tags from tagsfordroid.com.
They arrived fairly quickly and delivery was free of charge.
NFC Tags
To try things out I installed a lot of NFC reader and writer apps. To my dismay none of them seemed to work.
They could read the tags but could not write them. Having no idea how NFC worked, I was fairly disappointed :(

Searching the web I found out that this seems to be a bug in ICS:
http://code.google.com/p/android/issues/detail?id=22258

Googles sais that there will be a fix provided, but when, no one can tell.
So I tried to get it working myself...

If you would like to test the things below, install the latest PAW version (0.80) from the Android Market.

Disclaimer:
This solution is only working for Mifare Classic 1K tags!
If you try as described you do it at your own risk!
It's not my fault if your tags do not work afterwards!


Read the updates at the end of the post first!
This solution is no longer necessary and will most likely damage your tag(s), so do not use it!

The Problem
The problem (as reported in the bug report) seems to be that the tech reported by the tag do not contain
android.nfc.tech.Ndef which is necessary to write NDEF messages.

You can try this with the following code that you can insert into the BenShell Console of the PAW web application:

import de.fun2code.android.pawserver.AndroidInterface;
import android.nfc.NfcAdapter;

intent = AndroidInterface.getNfcIntent(5); // 5 secs timeout
if(intent != null) {
  tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  print(tag);
}
else {
  print("No Tag found!");
}

If the result is TAG: Tech [android.nfc.tech.MifareClassic, android.nfc.tech.NfcA] writing NDEF messages will not work!


The Possible Fix
As mentioned above I have no expirience with NFC, so I tried to find a solution that worked without having to know NFC or the Mifare tags in detail.

I searched the web and found some Mifare Classic 1K dumps at the nfc-tools site.
After downloading a dump I tried to write it to the tag. Interesting enough it seemd to work :)

After writing the dump, the above script reported:
TAG: Tech [android.nfc.tech.MifareClassic, android.nfc.tech.NfcA, android.nfc.tech.Ndef]

So here are the step by step instructions:
  1. Download the dump file from the nfc-tools site: mfc_1k__url_nfc-tools.mfd
  2. Save the dump file to the /sdcard of your android device.
  3. Start PAW and open the BeanShell console.
  4. Copy/paste the following script into the console:
    /*
    Mifare Classic - Write dump
    */
    import de.fun2code.android.pawserver.AndroidInterface;
    import android.nfc.NfcAdapter;
    import android.nfc.NdefRecord;
    import android.nfc.NdefMessage;
    import android.nfc.tech.*;
    import android.os.Bundle;
    
    
    intent = AndroidInterface.getNfcIntent(5); // 5 secs timeout
    print(intent);
    if(intent != null) {
      tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
      print(tag);
      
      if(tag != null) {
          nfc = MifareClassic.get(tag);
          nfc.connect();
        print("Is connected: " + nfc.isConnected());
    
      
         fis = new FileInputStream("/sdcard/mfc_1k__url_nfc-tools.mfd");
         
         b = new byte[16];
    
         errors = 0;
        
        sectors = nfc.getSectorCount();
        blocksPerSector = nfc.getBlockCountInSector(0);
        
        for(sector=0; sector<sectors; sector++) {
          print("Sector " + sector);
          
          for(block=0; block < blocksPerSector; block++) {
            logicBlock = nfc.sectorToBlock(sector) + block;
            $$.print("  Block: " + block + " ... ");
    
            fis.read(b);
    
            if(sector == 0 && block == 0) {
               print("Manufacturer block ... skipped");
               continue;
            }
    
            try {
              nfc.authenticateSectorWithKeyA(sector, MifareClassic.KEY_DEFAULT);
              nfc.writeBlock(logicBlock, b);
              print("ok");
            }
            catch(e) {
                print("error");
                errors++;
            }
          }
        }
        
        fis.close();
        nfc.close();
          print("--------------------------------------------------------------------------");
          print("Errors: " + errors);
          print("Dump written!");
       }
       else {
          print("Tag not supported!");
       }
    
    }
    else {
      print("No Tag found!");
    }
    
    
  5. Start the script and as soon as the PAW app is in foreground, place the tag at the back of the phone and leaf it there until the script is finished.
After that the tag is hopefully working.

Good luck and happy hacking :)

Update:
Ndef.getMaxSize() returns only 92 bytes that can be written to a NDEF message. I don't think that's normal :(

You can check this yourself with the following code:
import de.fun2code.android.pawserver.AndroidInterface;
import android.nfc.NfcAdapter;
import android.nfc.tech.Ndef;
 
intent = AndroidInterface.getNfcIntent(5); // 5 secs timeout
if(intent != null) {
  tag = intent.getParcelableExtra(NfcAdapter.EXTRA_TAG);
  print("Max NDEF message size: " + Ndef.get(tag).getMaxSize() + " bytes");
}
else {
  print("No Tag found!");
} 

Update 2:
The latest NFC TagWriter by NXP is able to format unformatted tags and fixes the problem.
Here is the Android Market link: NFC TagWriter by NXP

Thursday, December 1, 2011

PirateBox on Android

A while ago psundegroud created the thread [TUT]Roll Your Own PirateBox! PirateBoxMobile. At that time my only rooted device was an old Samsung Galaxy I7500 running Android 1.6. We managed to get this working. The two main issues were that WiFi Tether was needed, which didn't run on all Android devices and that only Ad-Hoc mode was available. The missing Infrastructure mode prevented most Androids from seeing the PirateBox hotspot.

Note: This post is somewhat outdated. The current state of the PirateBox can be found here: PirateBox Reloaded

Meanwhile I have a rooted Notion Ink Adam running AdamComb v0.3. So I tried to get PirateBox running by using the built in tether app. Built in tethering uses Infrastructure mode, which solves above mentioned problem.

On my Adam this is really working fine and I hope that it will also run on other Android units.
I'll divide the post in two parts. The first part describes the technical details, so if someone has problems in getting this to work this might be helpful for further testing.

Along with setting up the scripts I have also written a plugin for PAW which should make setup really easy.
If you are not interested in the technical details you can right jump to the Installing the Plugin section.

Techical Details

Setting up the PirateBox during boot is done by PAW startup scripts.

The main tasks of these scripts are:
  • Killing dnsmasq and restarting it with changed parameters, so that DNS requests always respond with the IP of the Android device.
  • Configuring iptables so that requests to port 80 are redirected to the port PAW is listening on, because PAW can not operate on privileged ports.
  • Change the PAW configuration to use the PirateBox Handler, which ensures that all unknown request are forwarded to the base URL (piratebox.org/).

The startup scripts are located in the subfolders 0 and 1 of the directory /sdcard/paw/etc/init. These folders represent PAW runlevels. Runlevel 0 is before the server starts and after server shutdown and 1 is after server start and before the server shuts down. Scripts beginning with S_ will be called on startup and scripts starting with K_ will be run at shutdown.

So here is a short summary what these scripts are doing.

0/S_PirateBox.bsh:
  • Checks if tethering is running. If not none of the steps below will be performed.
  • Kills the "original" dnsmasq process and starts a new one.
  • Executes iptables commands to get port redirection working.
  • Replaces the original PAW configuration with the PirateBox configuration.

1/S_SpirateBox.bsh:
  • Checks if tethering is running. If not none of the steps below will be performed.
  • Restores the original PAW configuration.
  • Displays a PirateBox notification.
  • Sets the max upload limit to 200MB.

1/K_S_SpirateBox.bsh:
  • Clears the PirateBox notification.
  • Kills dnsmasq process.
  • Stops tetherig and restarts WiFi.

0/K_S_SpirateBox.bsh:
  • Removes iptables rules for port redirection.

If you run into problems have a look at these scripts and try to run the commands inside the scripts individually.


Installing the Plugin

PAW Plug-in Menu
Installing the plugin is easy. I've put it on the PAW plugin page, so you'll find it within the PAW web application or you can direct download it from the PAW Plugin Page.
After download extract the ZIP file to the /sdcard/paw/html/app/plugins directory of your Android device.
Now after re-entering the PAW web application, the PirateBox plugin should be visible in the Plugins menu.

If the plugin setup screen shows a warning in red, the installation is likely to fail, because some prerequisites are missing.
If no warnings are displayed, press the Install button. You can also try to setup a PirateBox Access Point (AP) automatically. I'm not sure, if this is working on all devices. If it's not working, please create an AP manually.
To uninstall the plugin, press the Uninstall button.

PirateBox Plugin

It is important to note, that PirateBox will only be started if tethering is active before PAW starts up.
Otherwise the normal PAW configuration will be applied. So you can choose between these two configurations.

I hope that's not just working on my Adam but on many other devices as well.

Screenshost
Here are some screenshots from my setup...

PirateBox Startup

Connected to PirateBox

PirateBox on Nexus One
PirateBox in Chromium

Video

Here is a video that shows the PirateBox in action...



Update

Since version 0.3 of the plugin, PirateBox is also working on rooted Galaxy Nexus devices running Ice Cream Sandwich.
Here is a video...


Thursday, November 24, 2011

Running PAW on Port 80

PAW is only running on ports above 1023, because restricted ports are not permitted for normal apps.
For root users there is a solution by using iptables via the Superuser app.

I'm not sure if the below mentioned solution is working on all rooted devices.
Here is my configuration: Notion Ink Adam running AdamComb v0.3

If this works for you can easily be tested by copying the below code into the BeanShell Console of the PAW web application:

import de.fun2code.android.pawserver.util.*;

execRootShell(String[] commands) {
  shellCmd = "su -c sh";

  sh = Runtime.getRuntime().exec(shellCmd);
  os = sh.getOutputStream();

  for(cmd : commands) {
    os.write((cmd + "\n").getBytes());
  }

  os.write(("exit\n").getBytes());
  os.flush();
  sh.waitFor();  
}

/* ---- Config -------- */
ip = Utils.getLocalIpAddress();
redirectFrom = 80;
redirectTo = 8080;
action = "ADD"; // Can be ADD or DELETE
/* -------------------- */

action = "-" + action.substring(0, 1);

String[] iptablesCmds = new String[] {
  "iptables -t nat " + action + " OUTPUT -d 127.0.0.1 -p tcp --dport " + redirectFrom + " -j REDIRECT --to-ports " + redirectTo,
  "iptables -t nat " + action + " OUTPUT -d " + ip + " -p tcp --dport " + redirectFrom + " -j REDIRECT --to-ports " + redirectTo,
  "iptables -t nat " + action + " PREROUTING -d " + ip + " -p tcp --dport " + redirectFrom + " -j REDIRECT --to-ports " + redirectTo
};

execRootShell(iptablesCmds);

After executing the code, PAW should respond to requests on port 80.
If not, this solution seems not to be working on your configuration.

To forward port 8080 to port 80 every time PAW starts up, create a file called S_forward.bsh inside the /sdcard/paw/etc/init/0 directory.
In order to stop the forwarding when the PAW service shuts down, create an additinal file called K_forward.bsh and copy the same code.
You only have to change the line action = "ADD"; to action = "DELETE"; to stop the forwarding.

Hope that's not just working on the Adam...

PHP Plug-in Update

My cross compiled version of the PHP CGI was not working so well and I didn't manage to fix it.

Fortunately Klaas Biker informed me that Iulian Virtejanu has cross compiled a PHP CGI version that seems to work significantly better.
Here is Iulian's blog entry: PHP and Lighttpd for Android

Iulian gave me the permission to use it in PAW, so I have updated the PHP Plug-in to version 0.3.

There is one known issue:

system(...) does not work.
It is because system('pwd') actually invokes a hardcoded /bin/sh -c 'pwd' - and /bin/sh is not available on Android.
Iulian has submitted a bug report to PHP: https://bugs.php.net/bug.php?id=60081

If you find more bugs, please let me know. I will forward them.

Monday, October 31, 2011

Building PHP from Scratch

This post by Rahul Amaram describes how to build PHP for Android from scratch and how it can later be used within PAW.
I've added links to the needed files at the end of the post.

Thanks to Rahul for putting this together!

Here is Rahul's documentation...



It is suggested to use a VM for compilation so that you can take snapshots at regular intervals. Also as per google recommendation, it is suggested to use a VM with at least 8 GB RAM/swap and 12 GB of free hard disk.

Install Ubuntu 10.04 64-bit (select username as "joschi") on a VM.

After booting ubuntu, login as joschi.

Download and extract android-php

$ cd
$ wget -c "http://paw-android.fun2code.de/download/android-php.zip"
$ sudo apt-get install unzip
$ unzip android-php.zip

First initialize build environment. For donut, java 5 is needed. Also compiling with gcc/g++ 4.4 was throwing errors during compilation. Therefore we use gcc/g++ 4.3 for compilation.

$ sudo apt-get install python-software-properties
$ sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu hardy main multiverse"
$ sudo add-apt-repository "deb http://archive.ubuntu.com/ubuntu hardy-updates main multiverse"
$ sudo apt-get update
$ sudo apt-get install sun-java5-jdk


$ sudo apt-get install git-core gnupg flex bison gperf build-essential \
zip curl zlib1g-dev libc6-dev lib32ncurses5-dev ia32-libs \
x11proto-core-dev libx11-dev lib32readline5-dev lib32z-dev \
libgl1-mesa-dev g++-multilib mingw32 tofrodos python-markdown \
libxml2-utils


$ sudo apt-get install g++-4.3-multilib

Download Android source
This will be about 3 GB.

$ mkdir ~/bin
$ PATH=~/bin:$PATH
$ curl https://dl-ssl.google.com/dl/googlesource/git-repo/repo > ~/bin/repo
$ chmod a+x ~/bin/repo


$ mkdir ~/android-donut-src
$ cd ~/android-donut-src
$ repo init -u https://android.googlesource.com/platform/manifest -b android-1.6_r2
$ repo sync

It is a good idea to take snapshot of the VM here.

Build Android

$ cd /usr/bin
$ sudo ln -sf cpp-4.3 cpp
$ sudo ln -sf g++-4.3 g++
$ sudo ln -sf gcc-4.3 gcc
$ sudo ln -sf gcov-4.3 gcov
$ sudo ln -sf cpp-4.3 x86_64-linux-gnu-cpp
$ sudo ln -sf g++-4.3 x86_64-linux-gnu-g++
$ sudo ln -sf gcc-4.3 x86_64-linux-gnu-gcc


$ cd ~/android-donut-src
$ source build/envsetup.sh
$ lunch generic-eng
$ make -j4

It is a good idea to take another snapshot of the VM here.

Next build android-php. The patch is applied in order to avoid the error "undefined reference to `__sync_fetch_and_add_4'" during compilation.

$ cd ~/android-php
$ rm -rf ~/android-donut-src/bionic/libc/include/
$ unzip -d ~/android-donut-src/bionic/libc/ bionic_libc_include.zip
$ patch php-5.3.6/ext/standard/php_crypt_r.c < ~/sync_fetch_and_add.patch
$ ./build_php_5.3.sh

Finally copy php-5.3.6/sapi/cgi/php-cgi to /mnt/sdcard/paw/html/app/plugins/php_plugin/bin/ on your android phone (be sure to backup the existing php-cgi file), and install PHP using the PAW Web App.

References:



Files:
android-php.zip
sync_fetch_and_add.patch

Wednesday, October 19, 2011

Setting up a Cloud Print Printer using the Cloud-X API

The Cloud-X Java API makes it easy to set up a Google Cloud Print aware printer. In the latest API version Cloud Print push notifications are supported.


The below sample code shows the registration of a new printer (if not already present) and how to listen to push notifications.
For the sake of simplicity this is straight forward code, so there might be cleaner ways to do is ;)

Here is the sample code with comments:

/*
  * Process print job
  */
 public static void processJobs(CloudPrintConnection gcp, Printer printer) {
  try {
   List<Job> jobs = gcp.fetch(printer);
   if (jobs != null) {
    for (Job job : jobs) {
     System.out.println("Processing job " + job.getId() + " ("
       + job.getTitle() + ")");
     gcp.deletJob(job);
    }
   }
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static void main(String[] args) {
  try {
   String printProxy = "testprinter-" + NetworkUtil.getMacAddress();

   /*
    * Our printer
    */
   final Printer printer = new Printer();
   printer.setProxy(printProxy);
   printer.setName("Test Printer");
   printer.setStatus("Online");

   final CloudPrintConnection gcp = new CloudPrintConnection();
   gcp.connect(username, password, "cloudprint",
     "Cloud Print Test Client", null);

   /*
    * Register printer if not already present
    */
   List<Printer> printers = gcp.list(printProxy);
   if (printers.size() == 0) {
    System.out.println("Registering Printer with Proxy "
      + printProxy);
    gcp.register(printer, null);
   } else {
    printer.setId(printers.get(0).getId());
   }

   /*
    * Refetch all printer info
    */
   gcp.printer(printer);

   System.out.println("Printer Name: " + printer.getName());
   System.out.println("Printer ID: " + printer.getId());
   System.out.println("---------------------------------------------");

   processJobs(gcp, printer);

   /*
    * Wait for push notifications
    */
   PushReceiver pr = new PushReceiver("Cloud-X API - Push Receiver");
   pr.addListener(new PushListener() {

    @Override
    public void onReceive(String printerId) {
     if (printerId.equals(printer.getId())) {
      processJobs(gcp, printer);
     }
    }

    @Override
    public void onConnect() {
      System.out.println("Connected to Google Talk!");
    }

    @Override
    public void onDisconnect() {
      // Do nothing
    }
   });

   pr.connectPlain(username, password);

  } catch (Exception e) {
   e.printStackTrace();
  }

 }

Thursday, October 13, 2011

Cloud-X - Sharing Files through the Cloud

We just released the first alpha version of Cloud-X.
Cloud-X uses Google Cloud Print to share files between different users and devices. Along with the PC and Android app the Java API to access Google Cloud Print is also released.

This is a very early version, so there might be bugs.
The Android app and an online and stand alone PC version can be found at the following site:
http://cloud-x.fun2code.de

Comments welcome!

Update

The website is no longer online.
API with included sources can be downloaded from http://fun2code.de/downloads/GoogleCloudPrint.jar.

Monday, September 19, 2011

PAW and DynDNS

From time to time I get requests from users who would like to have a DynDNS integration into PAW.
I thought about it ... but it just doesn't make much sense, because there are enough apps in the Android Market that do exactly this.
So I will not provide a plug-in or another means of DynDNS integration, but for those of you who would like to experiment here is a basic BeanShell implementation of the DynDNS API:

import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.*;
import org.apache.http.client.methods.*;
import org.apache.http.util.*;
import org.apache.http.auth.*;

/*
    Host, username and password settings
*/
hostname = "";
user = "";
password = "";

/*
    Get IP number
*/
client = new DefaultHttpClient();  
getURL = "http://checkip.dyndns.com/";
get = new HttpGet(getURL);
responseGet = client.execute(get);  
resEntityGet = responseGet.getEntity();

if (resEntityGet != null) {
  // Extract IP number
  ip = EntityUtils.toString(resEntityGet).replaceAll(".*?([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}).*", "$1").replaceAll("[\n\r]", "");
  print("IP: " + ip);
 
 /*
    Send DynDNS update request
 */
  updateURL= "https://members.dyndns.org/nic/update?hostname=" + hostname + "&myip=" + ip + "&wildcard=NOCHG&mx=NOCHG&backmx=NOCHG";
  get = new HttpGet(updateURL);
  client.getCredentialsProvider().setCredentials(new AuthScope(null, -1),
new UsernamePasswordCredentials(user, password));
  responseGet = client.execute(get);  
  resEntityGet = responseGet.getEntity();
  
  response = EntityUtils.toString(resEntityGet);
  success = response.startsWith("good");
  
  // Print result
  print("Response: " + response);
}


Hostname, username and password have to be inserted into the script.

This implementation is not complete but should be a good start for further development.
To start the script automatically when PAW starts, put it in the paw/autostart or paw/etc/rc.* folder.

The API is quite easy and it should not take too long to get a complete implementation up and running ...

Thursday, September 15, 2011

PAW and DavDrive on Google TV

So now that the Google TV emulator is out and my Linux box supports KVM I tried PAW and DavDrive.

The only problem I had was that /sdcard was mounted read only and thus PAW could not extract its content and DavDrive could not write files.
To solve this issue I opened an ADB Shell and remounted the root file system via mount -o remount,rw /.

Apart from that everything worked fine. Compared to the Honeycomb emulation the Google TV emulation was quite fast.

Below are screen-shots of the two apps running on Google TV.

PAW running on Goolge TV

DavDrive running on Google TV

Wednesday, August 31, 2011

PAW - I18N & Native Mapper

It might seem that there is no process concerning PAW, but actually I'm quite busy :)
My plans are to release a 1.0 version this year. In October PAW will be two years under development and I think it's time to leave beta.

Things I'm currently working on are internationalization and what I call Native Mapping.

Internationalization
Currently I'm translating the web interface into German. This will result in a Java properties file with key and value pairs.
After this is finished it should be quite easy to make a translation for any language.
Translation takes much longer than I thought and I'm sure there will be a lot of mistakes in the first version.

Native Mapper
The PAW BeanShell approach has the advantage of being very flexible but when it comes to performance it's really slow.
As I showed in a previous blog post BeanShell is considerably slower than the same native code.
The Native Mapper is an approach to solve this dilemma.

The Mapper is comparable to a Servlet mapping in a JEE environment. The Native Mapper is implemented as Brazil Handler that sits in front of the BeanShell Handler and redirects URLs to native code (DEXed classes).

Handler Chain

The next version of PAW will use the mapper to speed up the generation of graphics inside the web application. This includes the generation of icons, contact images, album covers and the load graph.
Because some versions of HoneyComb have the problem that the DEX classloader is not working the original BeanShell code is also available. So if the Native Mapper fails to initialize (because the DEX classes are not available), the old BeanShell code is used.

A good example to show the possible speed-up when the Native Mapper is used is the Installed Apps page of the web application. On my Nexus One it shows 131 icons. Without Native Mapper this takes 90 seconds, with Native Mapper only 10.

Speed Test (time in seconds)

I hope to release the new version in the next couple of weeks ... stay tuned :)

Monday, July 18, 2011

DavDrive - Litmus Compliance Test

DavDrive works reasonably well with Linux, Mac and Windows clients. To make DavDrive more RFC 2518 compliant the Litmus compliance test was used.
Litmus contains a variety of tests. Because DavDrive implements only a subset of the RFC specification only three tests are relevant: basic, copymove and http

Litmus Test Results (left DavDrive 1.40, right DavDrive 1.41)
The upcoming DavDrive version 1.41 passes all three tests without failure.
This involved a significant code change, so intensive testing is necessary before publishing the new version.

Tuesday, July 12, 2011

PAW - Dynamic Handlers and Filters

In addition to dynamically loading DEXed classes the latest PAW version provides the possibility to use external so called dynamic Handlers and Filters.

Handlers and Filters are components of the underlying Brazil framework. Handlers are responsible for handling HTTP request. Filters in addition can process the output provided by an handler. In principle PAW consists of a number of handlers that are called in a row until a Handler feels responsible to handle the request.
There is a special Handler, the so called FilterHandler. This Handler can wrap a Handler and directs the output of that Handler to a list of Filters which process the response before it is provided to a Web Browser.

I'll demonstrate this on a small but useful filter that stamps all images inside a defined directory with the PAW logo. Such a technique is often used on sites that provide screenshots and want to automatically decorate them with a logo.

Image without and with Filter applied.

Note:
In this example only the AndroidDynamicFilter is used. There is also  a AndroidDynamicHandler available which can be used if a custom Handler is needed. For this example the AndroidDynamicHandler class is not needed.

Note on Honeycomb: As mentioned in an earlier post, this might not work on Android 3.x because of a bug in Honeycomb.

Prerequisites

What you will need to build the Filter:
Creating a Java Project

When Eclipse is running, we can create a standard Java project.
The Java build path should include the brazil.jar (I've renamed it from brazil-2.3.jar to brazil.jar) and the android.jar file. The android.jar files can be found in the installation directory of the Android SDK (platforms/android-*).

Java Build Path

The Filter Class

After creating the project create a source folder (if not available) and create a new package called dextest.filter.
Inside that package we will create the StampFilter class.

The structure should look something like this:

Project Structure
Before creating the StampFilter class just a view words how a filter works.
A Filter has three important methods:

  • init() - This initializes the Filter and is called on Filter startup (when PAW starts). This returns true on success and false on failure.
  • shouldFilter() - Is called on each Handler output and decides weather the response is to be filtered or not. Returns true if the filter() method should be called, false otherwise.
  • filter() - This is the actual filter method. It processes the content form the Handler and returns it.
Below is the code of the Filter class:

package dextest.filter;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;

import sunlabs.brazil.filter.Filter;
import sunlabs.brazil.server.Request;
import sunlabs.brazil.server.Server;
import sunlabs.brazil.util.http.MimeHeaders;

public class StampFilter implements Filter {
 private String prefix;

 private static final String STAMP_FILE = "stampFile";

 private static final String IMAGE_DIRECTORY_URL = "imageDirctoryUrl";

 private String stampFile;
 private String imageDirectoryUrl;

 public boolean init(Server server, String prefix) {
  this.prefix = prefix;

  Properties props = server.props;

  stampFile = props.getProperty(prefix + STAMP_FILE, null);
  imageDirectoryUrl = props.getProperty(prefix + IMAGE_DIRECTORY_URL, null);

  if (stampFile != null && imageDirectoryUrl != null) {
   if (!new File(stampFile).exists()) {
    server.log(Server.LOG_ERROR, prefix, STAMP_FILE
      + " does not exist!");
    return false;
   }

   if (!new File(imageDirectoryUrl).isDirectory()) {
    server.log(Server.LOG_ERROR, prefix, IMAGE_DIRECTORY_URL
      + " is not a directory!");
    return false;
   }

   return true;
  } else {
   server.log(Server.LOG_ERROR, prefix, "Missing parameter(s)");
   return false;
  }
 }

 public boolean shouldFilter(Request request, MimeHeaders headers) {
  String type = headers.get("Content-Type");
  return type != null && type.startsWith("image/png") && new File(request.url).getParent().equals(imageDirectoryUrl);
 }

 public byte[] filter(Request request, MimeHeaders headers, byte[] content) {
  try {
   return stampImage(content);
  }
  catch(Exception e) {
   request.log(Server.LOG_ERROR, prefix, "Exception: " + e.getMessage());
   return content;
  }
 }

 /**
  * This is the request object before the content was fetched
  */
 public boolean respond(Request request) throws IOException {
  return false;
 }

 byte[] getImageBytes(String image) throws IOException {
  File f = new File(image);
  byte[] bBitmap = new byte[(int) f.length()];
  FileInputStream fis = new FileInputStream(f);
  fis.read(bBitmap);
  fis.close();
  return bBitmap;
 }

 private byte[] stampImage(byte[] imageBytes) throws IOException {
  byte[] stampBytes = getImageBytes(stampFile);
  Bitmap stampBitmap = BitmapFactory.decodeByteArray(stampBytes, 0, stampBytes.length);

  Bitmap imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length);
  imageBitmap = imageBitmap.copy(imageBitmap.getConfig(), true);

  Canvas canvas = new Canvas(imageBitmap);
  canvas.drawBitmap(stampBitmap, imageBitmap.getWidth() - stampBitmap.getWidth(), imageBitmap.getHeight() - stampBitmap.getHeight(), null);

  ByteArrayOutputStream bos = new ByteArrayOutputStream();
  imageBitmap.compress(Bitmap.CompressFormat.PNG, 0 /*ignored for PNG*/, bos);

  return bos.toByteArray();

 }
} 

In the init() method you can see that the Filter excepts two parameters. One is called stampFile, the other imageDirectoryUrl. The stampFile parameter specifies the file (absolute path) of the image that is placed on top of the original image. The imageDirectoryUrl parameter specifies the URL for which the filter is applied.
The init() method does check if the parameters are present and if they are valid. The parameters are read from the configuration by using props.getProperty(). The prefix is basically the name of the Filter.

The shouldFilter() method checks if the file requested is a PNG and if the file is served from the URL that should be filtered.

Filtering is actually done in the filter() method. This method will not be discussed in detail, because it uses standard Android functionality to do the stamping.

After compiling the Filter, create a JAR file called filterTest.jar.

Creating the DEXed Filter JAR

Now we can use the dx command from the Android SDK to create a DEXed JAR file:
./dx --dex --output=/tmp/filterTest_dex.jar --positions=lines filterTest.jar

Now copy the resulting filterTest_dex.jar JAR file over to your Android device and store ist directly on the SD Card (/sdcard).

Here is the ready to use JAR file:  filterTest_dex.jar

The Stamp Image

In principle you can use any PNG image for stamping the images.
Here is an example:
Store it directly under /sdcard/paw_powered.png.

Modifying the PAW Configuration

The next step is to modify the PAW configuration. The two important files are handler.xml and filter.xml. Both files are located inside the paw/conf directory.
The handler.xml defines the Handles and the filter.xml defines the Filters used by PAW.
We will start with the filter configuration. For that open the filter.xml file and add the following Filter definition inside the <filters> </filters> tags:

<filter status="active" type="custom">
    <name>Stamp Filter</name>
    <description>Stamp Filter</description>
    <removable>true</removable>
    <id>stamp</id>
    <files />
    <params>
      <param name="stamp.class" value="org.paw.filter.AndroidDynamicFilter" />
      <param name="stamp.filterClass" value="dextest.filter.StampFilter" />
      <param name="stamp.filterJars" value="/sdcard/filterTest_dex.jar" />
      <param name="stamp.stampFile" value="/sdcard/paw_powered.png" />
      <param name="stamp.imageDirctoryUrl" value="/stamp" />
    </params>
  </filter>

PAW knows two filter types httpProxy and custom. HttpProxy filters are only important when PAW is used as a proxy server. In our case the StampFilter is a custom Filter.
The parameters define the Filter class to use, the location of the DEX file, the stamp image and the URL (the directory) where the images that should be stamped are located (we will create hat directory below).


Now comes the Handler part. Our new Handler definition will replace the existing File Handler definition and use the defined StampFilter. So edit the file handle.xml and replace the existing File Handler with the following XML code:
<handler status="active">
    <name>File Handler</name>
    <description></description>
    <removable>true</removable>
    <id>filehandlerWrapper</id>
    <files />
    <params>
      <param name="filehandlerWrapper.class" value="sunlabs.brazil.filter.FilterHandler" />
      <param name="filehandlerWrapper.handler" value="filehandler" />
      <param name="filehandlerWrapper.filters" value="stamp" />

      <param name="filehandler.class" value="sunlabs.brazil.server.FileHandler" />
      <param name="filehandler.root" value="/sdcard/paw/html" />
      <param name="filehandler.defaults" value="index.html" />
    </params>
  </handler>

After that restart the PAW app and let's start testing :)

Using the Filter

For testing, let's create the directory for the images that should be stamped as defined in the Filter definition. For that create a directory called /sdcard/paw/html/stamp and place some PNG images inside it. For a test, you can also put some other image files (e.g. JPG) in that directory. These should not be modified.
Now enter the Url http://<ip number>:8080/stamp into the address bar of your browser and select a PNG image.

The resulting image should include the stamp image in the lower right corner.

Monday, July 11, 2011

PAW - Dynamic DEX Class Loading

With PAW developers have the possibility to create dynamic pages using BeanSell scripts.
This is nice because pages can be developed very fast. On the downside BeanShell is not the fastest scripting language on the planet due to its heavy use of Java reflection. This was demonstrated in an earlier blog entry  where BeanShell code is between 10 and 50 times slower than the native equivalent.

One note beforehand:  Dynamic DEX class loading is currently not working on Honeycomb. This is due to a bug in the in the DexClassLaoder. Google has aknowledged the bug. This bug should be fixed in upcoming Honeycomb releases.

PAW now introduces a new directory called webconf/dex wich can contain JAR and APK files in DEX format. On server startup all files which are present in hat directory will be added to a new classloader. This classloader can be retrieved by calling getDexClassloader() on the service object. To make life easier the command useDexClasses() can be used in BeanShell pages.

To  convert an existing JAR file into a DEXed one the following dex command from the Android SDK (normally located in the platform-tools directory) can be used:
./dx --dex --output=output.jar --positions=lines input.jar

Note: In recent versions of the SDK the dx command has been moved to the build-tools/ directory of the SDK.

If you would like to keep the existing classes inside the JAR file, the additional option --keep-classes can be used. With this option the same JAR file can be used on Android or a PC running standard Java.

After the DEXed JAR file has been created it can be copied to the webconf/dex folder of the PAW installation.

Now to use the new classes you can either restart PAW or issue the following  command from the BeanShell Console:
server.props.get("serviceContext").buildDexClassLoader();

If the new classes should be used inside a BeanShell XHTML page the following command has to be executed:
useDexClasses()

That basically tells BeanShell to use the new classloader.

Example

Here is a very simple Java class that sums up two Intergers:
package dextest;

public class Math {
 public static int sum(int i, int j) {
  return i + j;
 }
}

After expopting the class as JAR file it can be converted into DEXed format using the following command (the JAR file dextest.jar is located in the /tmp directory) :
./dx --dex --output=/tmp/dextest_dex.jar --positions=lines dextest.jar

The resulting dextest_dex.jar JAR file is now placed inside the webconf/dex folder and PAW is restarted.

Now we can use the new class within the BeanShell Console like this:
useDexClasses();

import dextest.Math; 

print(Math.sum(1, 3));

Monday, June 6, 2011

Windows Performance and Filesize Limit

This weekend I had the chance to test DavDrive's performance when running Widows 7 (32bit).
I tested with Windows Explorer and BitKinex. The latter gave me a nice performance graph. My test file was a video file that was approx. 900MB in size.
The first things I notices was that Windows 7 seems to have a file size limit when it comes to WebDAV downloads. Therefore I immediately continued to test with BitKinex because I had no clue how to fix the size limit by that time. You'll find the solution for this after the BitKinex test.
BitKiney worked like charm. The graph shows the download speed.

DavDrive - BitKinex Performance


Speed was quite good (1,48 MB/s) and by far not as bad as many users have reported when using DavDrive on Windows.
So if you fix your Internet Explorer settings and the size limit, DavDrive should run fine and with reasonable speed on Windows.

Windows Size Limit
To fix the size limit you have to use the registry editor and edit the following key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters\FileSizeLimitInBytes

Monday, May 30, 2011

DavDrive - Tablet Support

There is a new version of DavDrive (1.39) and the full version got some enhanced tablet support.
The main screen now presents the usual start button on the left and a list that contains the log entries on the right.

DavDrive 1.39


In default mode the list only shows uploads and downloads so not to confuse the normal user too much. You can switch the display by pressing the small arrow in the activities title bar to display all entries.

Log View Selection


When clicking on these entries a dialog box with some more protocol details is displayed. If the file is present it can be opened.

Log Entry Details


The tablet support is not only for Honeycomb but for all others X-large screens. So other tablet users running Android 2.x should also benefit from the changes.

Log Broadcasts
Another new feature is that the log activities are broadcasted, which means that other programs/activities can listen to these broadcasts and react on these events.

For those interested, here are the details.

Intent Action: de.fun2code.android.webdrive.history.intent.add

ExtraTypeDescription
MethodStringMehthod name in uppercase.
TimestampLongTime in millis.
ResourceStringComplete resource (file) name.
SuccessBooleanTrue on success, otherwise false.
CodeIntegerHTTP result code.
MessageStringAdditional message.
TargetStringComplete target resource (file) name.
This is optional and only available when Method is MOVE.

Tuesday, May 17, 2011

Windows 7 WebDAV Performance

This bugged me for a while...
DavDrive's WebDAV performance seems o.k. on almost all platforms, except for Windows 7 (and especially the 64 bit version).
I have no Windows 7 at home, so I could not really test.

Today I stumbled over a blog entry by Chief Oddball which suggests a fix.
To make it short, here is the possible fix (quoted form the blog entry):
  1. In Internet Explorer, open the Tools menu, then click Internet Options.
  2. Select the Connections tab.
  3. Click the LAN Settings button.
  4. Uncheck the “Automatically detect settings” box.
  5. Click OK until you’re out of dialog hell.
I'm really interested if this is working.
Comments welcome!

Thursday, April 21, 2011

PAW Server & PHP

In this post I would like to write about what is coming up next in PAW development.
PAW supports BeanShell code to create dynamic pages. BeanShell is well suited for Android, because it integrates will into the OS by supporting the Android Java API.

The foundation of PAW is Brazil a web application framework which was developed by SUN (now Oracle). Although not widely used the framework has the advantage of being very small and flexible.
What I was thinking about for a while was to add PHP support. PHP is widely used  around the net. PHP's reputation is questionable and that are people who love and others who hate it. But it's undoubtedly one of the most used frameworks when it comes to web development.

PHP basically comes in two flawors, a CLI and a CGI version. To integrate PHP into the context of a web server the CGI version is needed.

Android PHP Project
Knowing that there is a CLI version provided by the Android PHP Project I headed over to their forum asking if they could provide a CGI version in addition to the CLI version.

Unfortunately the guys told me that a CGI version will not be provided by the project because it's not needed and pointed me to a page which describes shortly how to setup the tool-chain to do a cross compile of PHP.

Cross Compiling
So I tried to compile PHP myself. I have a Linux box at home so I'm quite familiar with doing configure/make and all this things but the last time I dug into C sources and header files is approx. 10 years back. So this was quite a challenge. Finally I got it working, but I think I did a lousy job. So if somebody can do it better, please go ahead and send me the php-cgi binary.

The CGI Handler
After I had the binary I extended and changed the CGI handler that is included with Brazil to work with Android. Due to some changes in the Brazil source that I have done concerning upload size limits the original handler could not be used. In the process I also tried to add SL4A support. I haven't tested this until know, but I hope it will be working.

SL4A Support
SL4A support is a little bit tricky, because one has to know the port the SL4A is listening to. So I looked around how to get hold of the port and found out that the port is reported in the logcat.
So on startup of the handler (on start of the PAW service) the handler looks into logcat and tries to find the SL4A host name and port number. These values are associated with the AP_HOST and AP_PORT environment variables. So SL4A the server has to be started shortly before PAW starts up.

The Plugin
PHP will be available as plugin shortly after the next release of PAW (which should be in the next month). PAW will then come with the new handler. Providing PHP as plugin has the advantage that PAW itself does not grow (much) in size. PHP is approx. 3 MB in size and so relatively large.
The plugin can be downloaded from an external website and extracted into the PAW plugin folder.
A new page showing available plugins will be included into the next PAW release.

Plugins in the Add-Ons menu


After extracting the plugin it should be visible under PAW's plugin menu.

Plugin in the Plug-ins menu


Installation of PHP should be a one click action and after a restart of PAW PHP should be available.

PHP Installation

PHP has been tested using a Google Nexus One and a Notion Ink Adam. The result of phpinfo() can be viewed here.

This is work in progress but it looks promising although not all PHP pages will be working but basic stuff should.

So stay tuned, I hope to finish this in a couple of weeks :)

Note:
PAW for Android 0.58 is now available at the Android Market.
The PHP plug-in is also available.

Update
If you have trouble downloading the PHP plugin from the original location, you can try the alternate link at Google Drive: PAW Plugins