Random Noise

cat /dev/random >> /dev/dsp

Archive for June 2010

Blackberry: ContentHandler and ApplicationMenuItem

with one comment

ContentHandler gives us the ability to register our application, so that our app is invoked automatically
when user clicks to open particular type of a file. ContentHandler uses ‘Server – Client’ model.
Client requests for processing a file type and Server processes it. All the application-filetype bindings are
stored in a Registry, the client must query this registry to find out if there are any handlers available for that
file type. Then invoke it.

To make our app a ‘Server’, we need to implement RequestListener. See below for sample implementation of a mp3 file handler.

It is important to register our app. It is recommended to perform/verify registration once during device startup.
You shoud pass an argument say “startup” during startup to differentiate from other entry points.
In eclipse this can be configured by editing the App_Descriptor.xml.

Also note that APP_IDs used for registration must be unique and failure to do so will render your app unusable.

package com.serialize.apps;

public class Mp3Player extends UiApplication implements RequestListener {
	public static final long MY_APP_KEY = 0x4a23b634ab2baf78L;
	public static final String MY_APP_CLASS = Mp3Player.class.getName();
	public static final String MY_APP_ID = "com.serialize.Mp3Player"
	private Invocation pending;
	private ContentHandlerServer server;
	
	public Mp3Player() {
		try {
			this.server = Registry.getServer(MY_APP_CLASS);
			//start listening formp3 invocation requests
			this.server.setListener(this);	
		} catch (ContentHandlerException e) {
		}					
	}

	public static void main(String[] args) {
		// On device startup register our app
		if (args != null && args.length > 0 && args[0].equals("startup")) {
			ensureRegistration();
		} else {			
			Mp3Player app = new Mp3Player();
			app.enterEventDispatcher();
		}
	}

	private static void ensureRegistration() {
		Object o = RuntimeStore.getRuntimeStore().get(MY_APP_KEY);
		// register only if not done already.
		if (o == null) {
			String[] types = {"audio/mp3"};
			String[] suffixes = {".mp3"};            
			String[] actions = {ContentHandler.ACTION_OPEN};
			String[] actionNames = {"Open with Mp3Player"}; 
			ActionNameMap[] actionNameMaps = {new ActionNameMap(actions,actionNames,"en")};


			Registry registry = Registry.getRegistry(MY_APP_CLASS);
		
			try {
				// Register our app as mp3 content handler
				registry.register(MY_APP_CLASS, types ,	suffixes , actions , actionNameMaps, MY_APP_ID, null);

				/* Register Menu Item here */
				new Mp3MenuItem().registerInstance();			
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	
	/* Utility method to perform invocation from other classes */
	public synchronized static void invoke(String className, String fileName, byte[] contents) {

		Invocation request = new Invocation();
		request.setID(MY_APP_ID); // request Mp3Player
		request.setURL(fileName);
		request.setData(contents); // set byte array if we have it
		request.setType("audio/mp3");

		// we don't care about the response
		request.setResponseRequired(false);

		try {
			Registry registry = Registry.getRegistry(className);
			registry.invoke(request);
		} catch (Exception ex) {
			System.out.println(className + ": Error occured while opening file");
		}
	}	
	
	public synchronized void invocationRequestNotify(ContentHandlerServer handler) {

		pending = handler.getRequest(false);
		
		if (pending != null) {
			byte[] contents = getContents();
			if (contents != null && contents.length != 0) {
				// TODO: Implement your Main screen and push it on the stack
				// remember that pushScreen is non blocking
				pushScreen(new Mp3PlayerScreen(contents));
				
				// notify the server that we are handling the invocation
				this.server.finish(pending, Invocation.OK);
			}
			else{
				// Close this instance or else you'll get "previous instance still active"
				// So we stop listening to invocation requests
				System.exit(0);
			}			
		} else {
			// Stop listening to invocation requests
			System.exit(0);
		}			
	}

	// Reads data from the invocation request
	// The invocation may contain associated byte array or URL to a file
	// or it could have associated stream (say network stream).
	private byte[] getContents() {
		InputStream is = null;
		StreamConnection sc = null;
		FileConnection fc = null;
		try {
			String filename = null;
			byte[] data = null;
			synchronized (this) {
			
				filename = pending.getURL();
				
				/* try to get associated byte array */
				if(filename.toLowerCase().indexOf("mp3") != -1) {
					data = pending.getData();
				}
				else {
				    // if file name did not end with mp3, don't handle it
					filename = null;
				}
			}
			
			// if we retrieved a byte array
			if (data != null && data.length > 0) {
				return data;
			} else if (filename != null) {
				
				// this is blocking call. use thread instead [e.g for network connection.]
				Connection conn = pending.open(false);
				
				// if this is a file connection the file is already on our phone
				// remember that a FileConnection is also a StreamConnection, 
				// so check FileConnection first				
				if (conn instanceof FileConnection) {
					fc = (FileConnection) conn;
					is = fc.openInputStream();
					
					return IOUtilities.streamToBytes(is);
				} else {
					sc = (StreamConnection) conn;
					is = sc.openInputStream();
					return IOUtilities.streamToBytes(is);
					// TODO: Save the array to SD Card?
				}

			}
		} catch (Exception ex) {
			System.out.println("Error occured while reading file.");
		} finally {
			try {
				if (is != null) {
					is.close();
				}
				if (fc != null) {
					fc.close();
				}
				if (sc != null) {
					sc.close();
				}
			} catch (Exception ex) {

			}
		}
		return new byte[0];
	}
}

Implementing the ApplicationMenuItem:

Although you may not need to implement a custom context menu item, it is particularly useful when there are
other third party applications already handling the same file type.

ApplicationMenuItem provides an alternate entry point, i.e a way to add a custom menu item when a specific application is running.
For example File Browser or Email app. To do this you have to register your implementation with ApplicationMenuItemRepository,
once registered that instance will be cached in memory until the device is on. But this repository gets wiped out every time the device is rebooted.

So it is a good practice to register once per startup(see above). Now, if you have a UIApplication and it has an icon, user will
start it by selecting it. In this case we have to make sure that we do not register our menu item twice. For this we will have to
use RuntimeStore to save state of our registration.

ApplicationMenuItem construct takes a context, if you are registering this menu item for specific files, mime type of the file type
must be passed as context. This especially important when you are registering your menu item with File Browser.

Important gotcha here is that when you request for an invocation, and the server is not running,
the server will be initialized before the invocation request is made.

package com.serialize.apps;

public class Mp3MenuItem extends ApplicationMenuItem {
	public static final long MP3_MENU_ITEM_KEY = 0x1a23b6c6ab2b1075L;

	public Mp3MenuItem() {
		/* will only be shown if the file mime type matches the context */ 
		super("audio/mp3", 500000); // order is found by trial and error method
	}

	/* This will be menu item text */
	public String toString() {
		return "Open MP3 File";
	}

	public void registerInstance() {
		/* verify Registration */
		Object o = RuntimeStore.getRuntimeStore().remove(MP3_MENU_ITEM_KEY);
		 if(o == null) {
			ApplicationMenuItemRepository amir = ApplicationMenuItemRepository.getInstance();
			// Gets Mp3Player descriptor, since this method is called from ensureRegistration()
			ApplicationDescriptor ad_startup = ApplicationDescriptor.currentApplicationDescriptor();
			ApplicationDescriptor ad_gui = new ApplicationDescriptor(ad_startup, new String[] {"menuitem_entry"});
			
			/* Register for File Browser */
			amir.addMenuItem(ApplicationMenuItemRepository.MENUITEM_FILE_EXPLORER, this, ad_gui);	 	
			RuntimeStore.getRuntimeStore().put(MP3_MENU_ITEM_KEY, this);
		 }
	}

	/* Mp3Player main() will be called before call to this function */
	public Object run(Object context) {
		if (context != null && context instanceof String) {
			String inputFile = (String) context;

			// excecution control is automatically passed to Mp3Player.invocationRequestNotify() after this
			Mp3Player.invoke(getClass().getName(), inputFile, null);
		}
		return context;
	}
}

Written by Vivek Unune

June 30, 2010 at 2:40 am

Posted in blackberry, Java

Tagged with ,