pagetop
Javablog
by Java coders, for Java coders RSS

Bluetooth Deployer

December 31st, 2007 by Sam

The test-develop cycle for writing J2ME applications can be significantly slowed down by the need to deploy the application onto the device. Given that most network operators charge extortionate data rates, the only real option is bluetooth deployment. In this post I present a solution that can be included as an Ant task. Note that it might not work for everyone as some operators and handset manufacturers intentionally disable bluetooth deployment of MIDlets.

In OS X, this is as simple as using the Bluetooth File Exchange application and sending both the jar and the jad (so that the digital signature can be used to set the correct permissions). However, this is very repetitive, non-scriptable and platform specific.

It occurred to me recently that the Bluetooth API for J2ME has implementations that work in J2SE… and there is no reason why I couldn’t write a Bluetooth MIDlet Deployer, for use in an ant task. In this post, I’ll give you the source and an example ant task for automating this annoying part of the J2ME development process. The source itself should work as a light introduction to putting multiple files on a device with the OBEX libraries of JSR-82.

Hat tip to Vlad Skarzhevskyy of Bluecove and MicroEmu who tells me that he has been using his Maven task obex-maven-plugin for 2 years.

UPDATE: (16th Jan 2008) This is now split across 3 files. The referenced files are available at BluetoothDeviceDiscover.java and BluetoothServiceDiscover.java. These are part of J2ME library I am working on. The source code to the file presented here is also available BluetoothDeploy.java.

First of all, you’ll have to get an implementation of JSR-82. I use Bluecove which works great on OS X and Windows. Avetana is available commercially for OS X. If you use Linux you may wish to check out BlueZ for the latest list of implementations, although future releases of Bluecove will support Linux.

Then you can bundle the 3 classes into a jar (or download my binary bluetoothdeploy.jar), invoking it using the command line

java -cp bluetoothdeploy.jar:jsr82.jar thinktank.j2me.j2se.BluetoothDeploy HWADDR app.jad app.jar

where HWADDR is your bluetooth device’s connection URL. This may be obtained by running the jar without any parameters.

On my phone (RAZR V3i), this works most of the time… but if I have an old copy of my jar/jad saved on the phone, it will throw an error if the jad is the first to arrive, or it will attempt to install the unsigned version if the jar is first to arrive. I typically clear out any old versions from the phone first.

If you’re using Ant, the following task should automate the process for you (make sure your paths are correct for your setup)

<!-- Deploy to a device using Bluetooth -->
<target name="deploy" depends="build">
    <java fork="yes" classname="thinktank.j2me.j2se.BluetoothDeploy" classpath="bluetoothdeploy.jar:bluecove.jar">
        <arg line="btgoep://0123456789ab:8;authenticate=false;encrypt=false;master=false" />
        <arg line="app.jad" />
        <arg line="app.jar" />
    </java>
</target>

If you’ve set up the build task to build your preverified jar and jad… deployment is a breeze! In my next post, I’ll go into more detail about setting up your IDE (and ant) for J2ME development without the buggy EclipseME.

Note that you can use this for sending any kind of file… I just wrote it with jar/jad specifically in mind.

/*
 * Copyright ThinkTank Mathematics Limited 2007
 *
 * This file is free software: you can redistribute it and/or modify it under the terms of
 * the GNU Lesser General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this file.
 * If not, see <http://www.gnu.org/licenses/>.
 */
package thinktank.j2me.j2se;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;
 
import javax.bluetooth.RemoteDevice;
import javax.bluetooth.ServiceRecord;
import javax.microedition.io.Connector;
import javax.obex.ClientSession;
import javax.obex.HeaderSet;
import javax.obex.Operation;
import javax.obex.ResponseCodes;
import thinktank.j2me.BluetoothDeviceDiscover;
import thinktank.j2me.BluetoothServiceDiscover;
 
/**
 * Application for sending a file over bluetooth, perfect for automatically deploying J2ME
 * MIDlets to devices as part of a J2ME Ant task.
 * 
 * @author Samuel Halliday, ThinkTank Mathematics Limited
 */
public class BluetoothDeploy {
 
    /**
     * Warning, this is very slow.
     * 
     * @param file
     * @return the MIME type for the given file
     * @throws IOException
     */
    public static String getMimeType(File file) throws IOException {
        // some typical cases that don't work
        String name = file.getName();
        if (name.endsWith(".jar"))
            return "application/java-archive";
        if (name.endsWith(".jad"))
            return "text/vnd.sun.j2me.app-descriptor";
 
        URL u = file.toURL();
        URLConnection uc = u.openConnection();
        return uc.getContentType();
    }
 
    public static void main(String[] args) throws IOException {
        if (args.length < 2) {
            // do a scan
            BluetoothDeviceDiscover deviceDiscover =
                new BluetoothDeviceDiscover();
            @SuppressWarnings("unchecked")
            Hashtable<String, RemoteDevice> devices = deviceDiscover.discover();
            for ( String name : devices.keySet()) {
                RemoteDevice device = devices.get(name);
                // attribute 0x0100 is the attribute for the service name element
                // 0x1105 is the UUID for the Object Push Profile
                BluetoothServiceDiscover serviceDiscover =
                    new BluetoothServiceDiscover(device, 0x0100, 0x1105);
                @SuppressWarnings("unchecked")
                Vector<ServiceRecord> records = serviceDiscover.discover();
                for ( ServiceRecord record : records) {
                    String url = record.getConnectionURL(
                        ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
                    System.out.println(name + " " + url);
                }
            }
            return;
        }
        // find the files
        List<File> files = new ArrayList<File>(args.length);
        for ( int i = 1; i <
            args.length; i++) {
            File file = new File(args[i]);
            if (file.exists() &&
                file.isFile())
                files.add(file);
            else
                System.err.println(file + " is not a file, skipping.");
        }
        BluetoothDeploy deployer = new BluetoothDeploy(files);
        // send them
        deployer.deploy(args[0]);
    }
 
    private final List<File> files;
 
    /**
     * @param files
     */
    public BluetoothDeploy(List<File> files) {
        this.files = files;
    }
 
    /**
     * Deploy the files to the given bluetooth address, using OBEX.
     * 
     * @param address
     * @throws IOException
     */
    private void deploy(String address) throws IOException {
        if (!address.startsWith("btgoep://"))
            throw new IllegalArgumentException("address " + address +
                " does not look like an OBEX address");
 
        ClientSession cs = (ClientSession) Connector.open(address);
        HeaderSet hs = cs.createHeaderSet();
        hs.setHeader(HeaderSet.COUNT, new Long(files.size()));
        HeaderSet response = cs.connect(hs);
        if (response.getResponseCode() !=
            ResponseCodes.OBEX_HTTP_OK) {
            System.err.println("Failed to connect " +
                response.getHeader(HeaderSet.DESCRIPTION));
            cs.close();
        }
        try {
            for (File file : files) {
                System.out.println("Sending " + file.getName() + " to " +
                    address);
                byte[] data = readFile(file);
 
                HeaderSet header = cs.createHeaderSet();
                header.setHeader(HeaderSet.NAME, file.getName());
                header.setHeader(HeaderSet.TYPE, getMimeType(file));
                header.setHeader(HeaderSet.LENGTH, new Long(data.length));
 
                Operation putOperation = cs.put(header);
                OutputStream outputStream = putOperation.openOutputStream();
                outputStream.write(data);
                outputStream.close();
                putOperation.close();
            }
        } finally {
            cs.disconnect(null);
            cs.close();
        }
    }
 
    /**
     * @param file
     * @return
     * @throws IOException
     */
    private byte[] readFile(File file) throws IOException {
        FileInputStream stream = new FileInputStream(file);
        byte[] bytes = new byte[(int) file.length()];
        try {
            stream.read(bytes);
        } finally {
            stream.close();
        }
        return bytes;
    }
}

This entry was posted by by Sam on Monday, December 31st, 2007 at 5:11 pm, and is filed under Ant, Bluetooth, J2ME, JSR-82, Java, MIDlet, Open Source, deployment. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.



4 comments on “Bluetooth Deployer”

To make app really useful you need to search for OBEX service on device or pass it as parameter.

The GUI application like this is here:

http://www.bluecove.org/bluecove-examples/obex-install/index.html

Just need to make it more command line friendly.

Do you want to work on it?

Hi Vlad, I intentionally don’t search for devices and services. A service search would be useful but that demands a device search first. That would mean:-

  • making the devices discoverable (not good for workflow… Motorola tend to only be discoverable for 60 seconds after user intervention)
  • making the tool interactive (not good for automation in an ant task)

But there would certainly be room for essentially creating a Java version of Bluetooth File Exchange. I’ve found it to be an invaluable tool.

Incidentally, I really like the OBEX Push with your application application of your tool :-) It’s a shame that (some form of) JSR-82 doesn’t come as part of J2SE. I guess that would drive all the network operators mad though… no data transfer charges for installing games!

Hi,

Not bad, but what I always wanted is something that actually deploys the application on the phone by the touch of a button on the PC, so that you dont have to press OK 5 times in a row. (As far as I know the MS .net compact framework SDK does this. At least that’s what I heard from a colleague of mine who tried it.)

That would need device support of course or some device dependent hacking. I was thinking something like installing the app the normal way then look at the folders where the real stuff went (jar file or its extracted contents) and then overwrite those using BT file transfer (e.g. FTP if the phone supports it). I’m not sure if it would work or not but it may be woth a try.

@atleta … what you call deploy is not what deploy means. Deploy means install on the device. It is entirely up to the device to decide what questions to ask to do that, and given the SSL certification, you’re not going to be able to do that without a quantum computer.

Leave a comment

Markdown is supported.

To include code snippets in your comment, use

<pre><code># lang java
... code here ...
</code></pre>

or use 4 spaces at the start of the line instead of using code and pre tags.

Comment feed: RSS