Auto-update of Midlets

Any professional application should be capable of updating itself over the internet. Even Midlets! The idea is fairly easy in that the Midlet needs to make a call to the server to check what version the latest software has, and compare that to the installed version. If there is a newer version available, then it needs to start the (mobile) device’s browser and point it at the URL of the new JAD file. The device will take care of the download and installation after that. So, the following article shows you exactly how this can be implemented.

First off, refer to the previous article, which described a framework for making server calls.

The sequence diagram looks like this:

Based on the framework, the following JSP can be installed serverside, which reads the version number out of the JAD file:


<%@page import="java.io.FileInputStream"%>
<%@page import="java.io.RandomAccessFile"%>
<%@page import="java.io.File"%>
<%@page import="java.util.List"%>
<html>
<body>
<%
RandomAccessFile raf = null;
try{
	String version = null;
	String path = request.getSession().getServletContext().getRealPath("index.jsp");
	File f = new File(path);
	f = f.getParentFile();
	f = new File(f, "jads/nameOfJad.jad");
	raf = new RandomAccessFile(f, "r");
	String curr = null;
	while((curr = raf.readLine()) != null){
		if(curr.startsWith("MIDlet-Version:")){
			version = curr.substring(16);
			break;
		}
	}
	%>OK
	<%=version%>|

	add other master data like the users details, their roles, etc. here...

	<%
}catch(Exception e){
	log.warn("failed to read master data", e);
	%>ERROR
	<%=e.getMessage() %>
	<%
}finally{
	if(raf != null) raf.close();
}
%>
</body>
</html>

This JSP is called when the Midlet starts, and effectively delivers “master data” to the device, such as the users roles, account details and importantly, the version number of the latest available JAD file on the server. Based on this version number, which is read as the MIDlet-Version property out of the JAD on the server, the client can decide if the currently installed version is up to date. The Midlet does this by calling the getAppProperty(String) method, passing it the String “MIDlet-Version”, which reads the version of the local JAD, that was used to install the current version.

Once the client has decided that there is a newer version available, it can ask the user if they wish to update by making an Alert the current screen. If the user chooses to install the update now, then all that is left is to open the devices browser to point at the new JAD which is online, and then close the application. Putting it together in a convenient method gives the following:


private void checkForUpdate(){
	final String currentVersion = getAppProperty("MIDlet-Version"); //eg 1.0.62
	new MasterDataGetter(currentVersion, model, this) {
		public void onSuccess(Object md) {
			MasterData masterData = (MasterData) md;
			model.setMasterData(masterData);

			if(currentVersion != null && masterData.getLastestVersion() != null){
				if(isUpdateAvailable(masterData.getLastestVersion(), currentVersion)){
					//does the user want to?
					
					Alert a = new Alert(
								"An update is available (version " + masterData.getLastestVersion() + ".\r\n" +
								"Would you like to download and install it now?");
					a.setType(AlertType.CONFIRMATION);
					a.removeCommand(Alert.DISMISS_COMMAND);
					final Command no = new Command("No", Command.ITEM, 1);
					final Command yes = new Command("Yes", Command.ITEM, 2);
					a.addCommand(yes);
					a.addCommand(no);
					a.setCommandListener(new CommandListener() {
						public void commandAction(Command c, Displayable d) {
							if(c == yes){
								try {
									platformRequest(Constants.getBaseUrl() + "jads/nameOfJad.jad");
									destroyApp(true); //coz we are performing an update!
								} catch (ConnectionNotFoundException e) {
									//tough titties :-( ie platformRequest couldnt be called
									e.printStackTrace();
									handleException("Failed to open browser to get installation files.", e, AlertType.ERROR);
								} catch (MIDletStateChangeException e) {
									handleException("Failed to destroy midlet", e, AlertType.ERROR);
								} finally {
									notifyDestroyed();
								}
							}else if(c == no){
								showCurrentView();
							}
						}
					});
					Display.getDisplay(Main.this).setCurrent(a, currentView);

				} //endif is update available?
			} //endif neither version is null

			//now we have already called maxant, and the user has confirmed that they are
			//happy to go online (eg BlackBerry shows them the URL of the first connection
			//attempt), we can continue to GA. we are trying to hide this fact from the user
			//as it may put of some users.
			gaUploader.track("start");

		}//end #onSuccess

		public void onError(int code, String result) {
			if(model.isShowDebugInfo()) sendDebugLog.callServer("error with update check " + code + ": " + result);
			//tough titties, dont offer to upgrade
		}

	}.callServer();
}

Copyright (c) 2010 Dr Ant Kutschera