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.