Monday, December 20, 2010

Sharing .... the Android Way

In the upcoming PAW Server for Android release there will be the possibility to share content between Android device and browser by using the integrated Android functionality of sharing content (images, contacts etc.).
Some apps provide a share or send to feature which pops up a selection dialog from which the app that should receives the content can be selected.

With the new PAW release it will be possible to receive and send content by using this functionality.

When the share functionality is enabled in PAW (which will be the default), PAW Server will be visible in the selection menu of any app providing the share feature.

Share menu in Gallery


After selecting the PAW Server menu entry an icon will be displayed in the PAW statusbar to signal that shared content is available:


Statusbar


After clicking on the icon (or the System->Share Content menu) the share page will be displayed, which presents the shared data in a table. When the download link is clicked, the shared content will be downloaded by the browser.

Share from web page

It will be also possible to send files to the Android device by uploading a file and calling the Android share or open URI function. With that it is for example possible to upload APK files and install them in one go without the hassle of uploading the file first and then running it in a separate step.


Upload from web page


For programmers there will be the possibility to access the shared content from within a web application. The shared data will be stored in the server scope of the PAW server.


Tuesday, December 14, 2010

Flash MP3 Player

This post demonstrates how the Android MediaStore can be used to play MP3 files directly from the device. As player front-end the Flash MP3 Player is used.

For those of you not interested in the code, there is a ready to install ZIP file at the end of the post.

Flash MP3 Player


All the files are mainly taken directly from the flashmp3player.org website.

Three changes were made:
  1. In the index.html the reference to flashmp3player.php has been changed to  flashmp3player.xhtml.
  2. The file flashmp3player.xhtml has been implemented.
  3. The file get_file.xhtml has been copied from the PAW app directory and the PAW access control has been removed. This file provides access to files that are not located within the html root directory of the server. This is a potential security risk, so make sure to protect the flashmp3player folder with a password.

The implementation of flashmp3player.xhtml uses the MediaStore to extract the information about artist, title and file and constructs a XML file for the Flash MP3 Player.

An example XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<playlist>
<song id="120"  title="Beat The Bastards" artist="Accept" src="get_file.xhtml?file=%2Fmnt%2Fsdcard%2FMusic%2FAccept%20-%20Blood%20Of%20The%20Nations%2F01%20-%20Beat%20The%20Bastards.mp3" />
<song id="121"  title="Teutonic Terror" artist="Accept" src="get_file.xhtml?file=%2Fmnt%2Fsdcard%2FMusic%2FAccept%20-%20Blood%20Of%20The%20Nations%2F02%20-%20Teutonic%20Terror.mp3" />
<song id="122"  title="The Abyss" artist="Accept" src="get_file.xhtml?file=%2Fmnt%2Fsdcard%2FMusic%2FAccept%20-%20Blood%20Of%20The%20Nations%2F03%20-%20The%20Abyss.mp3" />
<song id="123"  title="Blood Of The Nations" artist="Accept" src="get_file.xhtml?file=%2Fmnt%2Fsdcard%2FMusic%2FAccept%20-%20Blood%20Of%20The%20Nations%2F04%20-%20Blood%20Of%20The%20Nations.mp3" />
</playlist>

Below is the code of the flashmp3player.xhtml file:
<bsh>
import android.net.Uri;

uri = android.provider.MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;

String[] tags = new String[] {
android.provider.MediaStore.Audio.Media._ID,
android.provider.MediaStore.Audio.Media.TITLE,
android.provider.MediaStore.Audio.Media.ARTIST,
android.provider.MediaStore.Audio.Media.ALBUM,
android.provider.MediaStore.Audio.Media.DATA
};

service = server.props.get("serviceContext");

cursor = service.getContentResolver().query(uri, tags, null, null, android.provider.MediaStore.Audio.Media.ARTIST + "," + android.provider.MediaStore.Audio.Media.ALBUM);

print("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
print("<playlist>");
cursor.moveToFirst();

do{

  id = cursor.getString(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Audio.Media._ID)).replaceAll("\"", "");
  artist = cursor.getString(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Audio.Media.ARTIST)).replaceAll("\"", "");
  title = cursor.getString(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Audio.Media.TITLE)).replaceAll("\"", "");
  file = cursor.getString(cursor.getColumnIndexOrThrow(android.provider.MediaStore.Audio.Media.DATA));
  file = "get_file.xhtml?file=" + Uri.encode(file);

  if(file.endsWith(".mp3")) { 
 print("<song id=\"" + id + "\"  title=\"" + title + "\" artist=\"" + artist + "\" src=\"" + file + "\" />");
  }
}

while(cursor.moveToNext());
cursor.close();
print("</playlist>");
</bsh>

Downloads:
paw_flashmp3player.zip

Installation:
Unzip into the /sdcard folder of your Android device.
Access http:<ip>:8080/ on your device. The MP3 Player entry should be available in the list of applications.

Attention: There are no access restrictions on the installation directory!
So use this only for a short period of time or password protect the directory by defining a password (PAW Menu -> Server -> Directory Protection).

Uninstall:
Delete the folder /sdcard/paw/html/flashmp3player and the file /sdcard/paw/webconf/apps/x02_flashmp3player.conf.

File Upload

Since PAW 0.51 file upload sizes are no longer restricted. This restriction has been removed by changing the code of the underlying Brazil framework.
This post shows how file upload can be implemented.

A ready to install ZIP file containig all the code is available at the end of the post.

Since PAW 0.51 requests with content-type multipart/form-data are handled differently. The post request parameters are not handled automatically and processing is left to the application. The InputStream of the request can be accessed by using the instance variabel request.in.

The example consists of two files. One is the html file containing the form which allows the upload of two files.
The second file is the xhtml file containing the code to save the uploaded files to the /sdcard directory of the Android device.

To make the handling of the multipart form data easier the class MultipartStream from the Apache Commons Fileupload package has been added to PAW.
Actually this is an old version of the class which has no dependencies at all.

The html which contains the form is quite simple:

<html>
<head>
  <title>File Upload</title>
  <link rel="stylesheet" href="css/default.css">
</head>
<body>
  <h2>File Upload Demo</h2>
  <p>
  Uploads files to /sdcard...
  </p>

  <form action="fup.xhtml" method="post" enctype="multipart/form-data" >

  File to upload: <input name="upfile1" type="file"><br>
  File to upload: <input name="upfile2" type="file"><br>
  <input value="Upload" type="submit">
  </form>
</body>
</html>

The second file performs the following steps to save the files:
  1. Extract the boundary string from the content-type.
  2. Constructs a MultipartStream object.
  3. Skips the preamble. This ignores all data until the boundary is reached.
  4. Reads the parts until it finds a file. If a file is found it extracts the filename and stores the file in the /sdcard directory.
  5. If the part contains no file, the part's body is skipped.
There is a nice article that discribes this in more detail on oreillynet.com: Parsing form-data multiparts

So now here is the code:

<html>
<head>
<title>File Upload</title>
<link rel="stylesheet" href="css/default.css">
</head>
<body>
<bsh>
import org.paw.util.*;
import org.apache.commons.fileupload.*;

outDir = "/sdcard/fup";
files = new ArrayList();
type = (String) request.headers.get("content-type");

if (type != null &&type.startsWith("multipart/form-data")) {
 try {
  // Get the boundary string
  boundaryIndex = type.indexOf("boundary=");
  byte[] boundary = (type.substring(boundaryIndex + 9)).getBytes();

  // Construct a MultiPartStream with request.in as InputStream
  MultipartStream multipartStream =  new MultipartStream(request.in, boundary);
  boolean nextPart = multipartStream.skipPreamble();

  // Loop through all parts
  while(nextPart) {
    headers = multipartStream.readHeaders();

    // If part is a file, save it to disk. Otherwise skip it.
    if(headers.contains("filename=\"")) {
       // Get filename
       filename = headers.substring(headers.indexOf("filename=") + 10);
       filename = filename.substring(0, filename.indexOf("\""));

       // If filename is not empty save content to filesystem
       if(filename.length() > 0) {
   files.add(filename);
   multipartStream.readBodyData(new FileOutputStream(outDir + "/" + filename));
       }
                     else {
   multipartStream.discardBodyData();  
       }
    }
    else {
       multipartStream.discardBodyData();
    }

    nextPart = multipartStream.readBoundary();
  }
 }
 catch(e) {
  print(e);
 }
}
</bsh>
Uploaded files to <bsh>$$.print(outDir);</bsh>:<br>
<hr>
<bsh>
for(file : files) {
 print(file + "<br>");
}
</bsh>
</body>
</html>

Downloads:
paw_fup.zip

Installation:
Unzip into the /sdcard folder of your Android device.
Access http:<ip>:8080/ on your device. The File Upload Demo entry should be available in the list of applications.

Attention: There are no access restrictions on the installation directory!
So use this only for a short period of time or password protect the directory by defining a password (PAW Menu -> Server -> Directory Protection).

Uninstall:
Delete the folder /sdcard/paw/html/fup and the file /sdcard/paw/webconf/apps/x01_fup.conf.

Thursday, November 25, 2010

DavDrive and Nautilus

If you are using DavDrive in combination with Ubuntu you might notice a bug which prevents you from copying directories from your Android device when using Natilus.
This is not a DavDrive bug but a Nautilus/gvfs bug which is present for more than two years.

For more detail see the Ubuntu bug report #267642.

When copying a directory with Natilus only a file containing the directory listing is produced:


Result of Nautilus directory copy



The bug report mentions a workaround:
Copying directories is possible by using a shell and accessing the ~/.gvfs folder directly.

Directory copy from Shell

Hopefully this nasty bug will be fixed soon.

Wednesday, November 17, 2010

DavDrive & DAVCommander

DavDrive allows the browsing of the filesystem from a browser, but this is not very comfortable and does not allow for file upload, file deletion or to create and delete directories.

DAVCommander is a Sourceforge project, that provides a nice WebDAV frontend which can also be used as a Java Applet inside a web browser.
This following blog post shows how DAVCommander can be easily integrated into DavDrive (and DavDrive Lite).

The idea is to put the files needed by DAVCommander (HTML and JAR file) into a folder served by DavDrive and call the HTMLfrom a browser. The HTML contains a JavaScript that builds the <applet> tag with the appropriate WebDAV URL, so that the configuration is done automatically. For all this to work Java has to be installed on your PC and Applets have to work.

For those interested, here is the HTML page:

<script type="text/javascript">

davURL = window.location.protocol + "//"  + window.location.host;

document.write('<body style="background: gray;"><div style="border: 1px solid black;">');
document.write('<applet archive="DAVCommander.jar" code="ch.oxinia.webdav.davcommander.AppletMain.class" width="100%" height="98%">');
document.write('<param name="uri" value="' + davURL + '">');
document.write('<param name="staticUri" value="true">');
document.write('<param name="acl" value="false">');
document.write('<param name="versionControl" value="false">');
document.write('<param name="locking" value="false">');
document.write('<param name="davproperties" value="false">');
document.write('<param name="showTrees" value="false">');
document.write('<param name="multipleSelection" value="true">');
document.write('<param name="dragDrop" value="true">');
document.write('<param name="renameWithDoubleclick" value="false">');
document.write('<param name="lockUiWhileBusy" value="false">');
document.write('<param name="keepAlivePeriod" value="0">');
document.write('<param name="keepAliveUri" value="">');
document.write('</applet>');
document.write('</div></body>');
</script>

Don't worry, installation is very simple and all you need is a simple ZIP file which is provided at the end of the post that has to be extracted to a directory served by DavDrive.

In my example, I'll extract the ZIP file to my /sdcard on my Android device which is also the root folder of my DavDrive setup. After this is done direct the browser to the URL displayed by the DavDrive app, enter user/password and browse to the DAVCommander Folder.

 
Directory Listing



After selecting the file DAVCommander.html the Applet should be started and after providing user/password (could be more than once) DAVCommander should appear.


DAVCommander


That's it, now you can mange files and folders directly from within your browser.

Downloads:
DAVCommander.zip

Wednesday, September 22, 2010

PAW Performance

To test the performance of PAW on Android I created a small web app for PAW Runtime which basically takes a small picture (80x120px) and transforms it into a grayscale and sepia image.

Web App


The transformation was done with native code (compiled into the PAW runtime) and also with pure BeanShell code to get a feel how BeanShell performs.
Main reasons for doing the test were the reports about bad BeanShell performance.

The algorithms used for BeanShell and native code  were identical.
Here is the BeabShell code:

import android.graphics.*;
import android.graphics.Bitmap.CompressFormat;

file = parameters.get("file");
filter = parameters.get("filter");
sepia = (filter != null && filter.equals("sepia")) ? true : false;

depth = 20;

if(file != null) {
 bitmap = BitmapFactory.decodeFile(file);

 if(bitmap != null) {
  bitmap2 = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);
  w = bitmap.getWidth();
  h = bitmap.getHeight();
  for(x=0; x < w; x++) {
   for(y=0; y < h; y++) {
    c = bitmap.getPixel(x, y);

    r = Color.red(c);
    g = Color.green(c);
    b = Color.blue(c);
     
    gry = (r + g + b) / 3;
    r = g = b = gry;
    
    if(sepia) {
     r = r + (depth * 2);
     g = g + depth;
    }

    if(r > 255) {
      r = 255;
    }
    if(g > 255) {
      g = 255;
    }

    bitmap2.setPixel(x, y, Color.rgb(r, g, b));
   }
  }

  bos = new ByteArrayOutputStream();
  bitmap2.compress(CompressFormat.PNG, 0 /*ignored for PNG*/, bos);
  byte[] bitmapdata = bos.toByteArray();

  request.sendResponse(bitmapdata, "image/png");
  request.out.flush();
  request.out.close();
 }
}


Here is the result after running the test:

Result


Actually BeanShell is much slower than I expected. But if you take into consideration what is happening behind the scenes it's not as surprising as it seems. There is a log of Java reflection, parsing, instantiations etc. going on.
Another result was, that the PAW server itself is performing nicely, which is a good sign.
So from a developer's point of view BeanShell code should be reduced to a minimum and native Android API should be used as much as possible.

What it also means is that, because the PAW Web App uses much BeanShell code, there is room for improvement...

To test it for yourself, download the latest PAW Runtime version and unpack the Image Manipulation ZIP file to your /sdcard/paw-runtime directory.

After that the following menu entry should be available in your PAW Runtime menu:

Menu Entry

Thursday, September 16, 2010

PAW Runtime

PAW Runtime is basically a stripped down PAW Server that only listens to the loopback interface on a random port and with a WebView serving as browser frontend.
With this set-up it is possible to run PAW applications directly on the device.

Everything that's possible with PAW Server is also possible with the runtime version.

Overview


It enables users and programmers to directly develop applications on the device without the need to install a SDK on the PC.
Because the GUI is defined via HTML development of the interface is easy for people that know HTML/CSS and JavaScript.

There are of course limitations to this approach concerning speed and using all features of the platform, but for developing small applications or prototypes this should be sufficient.
What is not possible is to develop individual apps, all PAW Runtime apps reside in the /sdcard/paw-runtime directory and are accessible (when defined in the webconf/apps directory) from the main menu screen.

The runtime itself comes with no applications, but demo applications are available for download.
Below is the main menu with installed demo applications.

Menu

Let's have a look at the compass app shown in the first image.

All applications are stored in the /sdcard/paw-runtime/html folder. The application consists of a XHTML file and the corresponding jQuery scripts and images. Together with the demos comes a html/common folder that contains components (like JavaScript files) that can be reused.

Let's have a look at the XHTML file:

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>Compass</title>
<link rel="stylesheet" href="../common/css/default.css">
<script type="text/javascript" src="../common/js/jquery.js"></script>
<script type="text/javascript" src="../common/js/executeScript.js"></script>
<script type="text/javascript" src="js/jquery.rotate.js"></script>
<script type="text/javascript">
$(document).ready(function() {
updateCompass();
});

function updateCompass() {
executeScript('compassScript', null, 'compassRes');
$("#compassImg").rotate($("#compassRes").val() * -1, 'abs');
}
</script>
</head>
<body>
<center>
<h1>Compass</h1>
<img src="images/compass_rose.png" id="compassImg">
<p>
<div style="font-size: 30px; font-style: bold;" id="compass"></div>
</p>
</center>

<input type="hidden" id="compassRes" onChange="$('#compass').html($(this).val() + '°'); setTimeout('updateCompass();', 500);">

<!-- Script -->
<textarea id="compassScript" style="visibility:hidden; display: none;">
import de.fun2code.android.pawserver.AndroidInterface;

sensorListener = AndroidInterface.getSensorListener();
bearing = sensorListener.getOrientBearing();
$$.print(bearing);
</textarea>

</body>
</html>


The part that reads the bearing information of the device is surrounded by BeanShell <bsh>...</bsh> tags.
All the rest is standard HTML/JavaScript. A documentation of the functions provided by PAW comes bundled with the demo applications.

One last thing to do is to define the application's .conf file in the webconf/apps directory, so that the application is available from the main menu.

name=Compass
description=Displays Compass and Bearing
icon=compass/images/compass_rose.png
href=compass/compass.xhtml

APK Download: PawRuntime.apk
Demos Download: PawRuntime_Demos.zip
Eclipse Project: AndroidPawRuntime_Eclipse_Project.zip

Note: Files are based on PAW Server for Android 0.60 beta


The demos can be simply unpacked onto the /sdcard of the device.

PAW Runtime is currently in alpha state.
Comments are welcome...

Friday, June 4, 2010

Sending Web Pages, Market Links and Goolge Map Routes directly to the Phone

With the new version of PAW (0.38.6) it is now possible to send web pages, market links and Google Maps (routes, Street View) directly to the Android phone*.

The setup is easy. After installing Greasemonkey and two additional scripts (one for the Android Market and one for the web an Google Map pages) the only thing that is left is to configure the Greasemonkey scripts. This is not very hard and should only take a view seconds.

The scripts and instructions are found within PAW's web application, in the Add-Ons section.

Below are some screen shots show the new functionality.


* Google Maps might only be working on devices which are running Android 2.1 and up

Thursday, March 4, 2010

SSL Again

I finally got SSL working in PAW.

The code in the previous post is working. The problem was fixed by disabling keep alive in the Brazil framework.
 Actually I can't tell if this is a problem with the Brazil framework or Android.

HTTPS can now be enabled in the PAW server settings.


Anyway it's working and performance does not seem to suffer too much.

Tuesday, March 2, 2010

PAW Server for Android and SSL

After receiving user feedback from Stephen I decided to have a look how to implement SSL connections for PAW.

As Stephen pointed out, and of course he is correct, security is not very high in PAW as it is possible to modify data on the device.
First thing I did was to check how SSL works on standard Java.
This was fairly easy and worked on first try:

int port = 9999;
System.setProperty("javax.net.ssl.keyStore", "android-keystore");
System.setProperty("javax.net.ssl.keyStorePassword", "android");
ServerSocketFactory ssocketFactory = SSLServerSocketFactory.getDefault();
ServerSocket ssocket = ssocketFactory.createServerSocket(port);
Socket socket = ssocket.accept();

Unfortunately that didn't work at all on Android. I only received an normal ServerSocket without any SSL support.
After some googeling the code looked like this:

import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.KeyManagerFactory;
import java.security.KeyStore;

try {
    int port = 9999;
    char[] passphrase = "android".toCharArray();

 ctx = SSLContext.getInstance("TLS");

 kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
 //BKS
 ks = KeyStore.getInstance (KeyStore.getDefaultType());

 ks.load(new FileInputStream("/sdcard/pawKeystore"),passphrase);
 kmf.init(ks, passphrase);
 ctx.init(kmf.getKeyManagers(), null, null);

 ssf = ctx.getServerSocketFactory();
 ssocket = ssf.createServerSocket(port);
 ssocket.accept();

} catch (e) {
    print(e);
}

This code is in principle working. Actually the code can be executed inside the PAW BeanShell Console (if a keystore exists).

What I didn't know was that the keystore format on Android is not the same as in standard Java. The format uses by Android is BKS which is not supported by the Java keytool out of the box.

To get BKS working the JCE provider from BouncyCastle has to be downloaded and the JAR file has to be extracted to $JAVA_HOME/jre/lib/ext/. After that the following line has to be added to the file $JAVA_HOME/jre/lib/security/java.security:

security.provider.9=org.bouncycastle.jce.provider.BouncyCastleProvider

The number behind security.provider. depends on your Java installation. After that a keystore file can be created with the following command line:

keytool -genkey -keyalg RSA -keysize 1024 -alias alias -keystore pawKeystore -validity 3650 -storetype BKS

On my Galaxy I noticed that when running the code not all connections were accepted, which was strange. In addition there would be minor changes necessary to the underlying Brazil framework, because SSLSockets throw additional Exceptions which do not occur when standard  Sockets are used.

But after setting it all up it didn't work because of connection problems and when it worked from time to time it was horribly slow.
The connections problems might be a problem within Android, because for the application this all should be transparent.

I would have really liked to implement SSL within PAW but at the moment I just don't get it working.