Blackberry: ContentHandler and ApplicationMenuItem
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;
}
}
MSBuild Task: Solution and Project dependencies
Few months ago I needed to use MSbuild to build solution. Sounds simple right? Yeah, but not quite.
Somehow, my msbuild failed with project dependency issues. It turns out that MSBuild doesn’t respect project dependencies in a solution. Google “msbuild solution dependencies” and you’ll find various post complaining about the problem with MSBuild.
I modified the MSBuild task found here to get projects in a solution file in dependency order.
You can then call the task to get the projects and compile them in order:
<Target Name="BuildProjects"> <GetProjecsInOrder Solution="$(MSBuildProjectDirectory)\MySolution.sln"> <Output ItemName="ProjectFiles" TaskParameter="Output"/> </GetProjecsInOrder> <MSBuild Projects="%(ProjectFiles.FullPath)" Targets="Build" Properties="Configuration=Release"/> </Target>
You can download the modified source from http://www.esnips.com/doc/8e232db4-21ac-4d93-90dc-2a8bc4db34a8/MSBuildTasks
IDataReader GetValue Extension method
It is offten desired to get a value from DataReader using column name with known Type. For example you want to get a string value of column ‘first_name’ and int value of column ‘account_number’. The following extension method makes it easy to fetch values from a DataReader using Type and column name:
public static class ReaderHelper
{
public static bool IsNullableType(Type valueType)
{
return (valueType.IsGenericType &&
valueType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
}
public static T GetValue<T>(this IDataReader reader, string columnName)
{
object value = reader[columnName];
Type valueType = typeof(T);
if (value != DBNull.Value)
{
if (!IsNullableType(valueType))
{
return (T)Convert.ChangeType(value, valueType);
}
else
{
NullableConverter nc = new NullableConverter(valueType);
return (T)Convert.ChangeType(value, nc.UnderlyingType);
}
}
return default(T);
}
}
It can be used as:
User GetUser(IDataReader reader)
{
User user = new User();
user.FirstName = reader.GetValue<string>("first_name");
user.LastName = reader.GetValue<string>("last_name");
user.Email = reader.GetValue<string>("email");
user.AccountNumber = reader.GetValue<int>("account_number");
user.NumberOfVehiclesOwned = reader.GetValue<int?>("vehicle_count"); // nullable data field
return user;
}
Experiments with OpenWRT and USR5465
I recently purchased U.S Robotics USR5465 router. I chose it because it had 4MB flash and 16MB ram; enough to run linux based distribution for router such as OpenWRT. Out of the box, this router runs linux kernel 2.4 along with busy box. But I was very much interested to run latest kernel (2.6.x) on it. With OpenWRT it is easy to create the development environment. Just login to your favorite linux as a non-root and follow few steps mention here. Once you configure your router kernel and run make command. OpenWRT automatically downloads required toochain source and compiles it for your PC.
USR5465 has one serial port and possibly a 12 pin JTAG. So, I brought myself a Nokia DKU-5 cable (SN: WT048000317). This cable has a usb to serial converter; and it’s cheap! I cut/removed the phone connector end of the cable. After a little research, I found out that:
Phone connector Pin# Wire Color Signal
8 RED GND
7 GREEN TX
6 WHITE RX
4 ORANGE 3.3 OUT (Not used, do not connect!!)
After connecting wires to jumper J301, compiling ark3116 kernel module and using 115200 baud rate with 8-N-1, I was able to connect my laptop to the router. When I restarted the router I saw the boot messages from router. Here is how I connected the wires:
j301 |o| ---> GREEN (TX) |o| ---> WHITE (RX) |o| |o| ---> RED (GND) ______ o -> Power led ################################# <--- Motherboard edge
Another advantage of this router is that it has a USB 2.0 port. To use usb, I had to use revision 13088 and patched it using this patch
svn checkout -r13088 https://svn.openwrt.org/openwrt/trunk kamikaze
patch -p1 < kamikaze-r13088-b43ssb-2.6.27.diff
I had to change “BUSWIDTH 2″ to “BUSWIDTH 1″ in drivers/mtd/maps/bcm47xx-flash.c file in order to get a working image. Otherwise I saw few “error -5″ lines after booting.
I wasn’t able to connect to the internet, even after I got the image to boot successfully. I found that the problem was due to incorrect labeling of the WAN and the LAN ports by OpenWRT. Since I was able to connect to the router using the serial port I changed the VLAN config in /etc/config/network to:
#### VLAN configuration
config switch eth0
option vlan0 "0 1 2 3 5*"
option vlan1 "4 5"
Only then I was able to get a valid IP address and connect to the internet.
Other than wireless (which didn’t bother me, as I always use wired connection) everything is now working perfectly. Since this model is not supported by OpenWRT, it doesn’t know the led configurations and does not recognize the model. So I had to add that info. in diag.c file. gpioctl utility was useful to find out the led and reset button gpios.
You can download modified diag.c from here. Once you compile you should see WAN led and USB led litup (need to do some configuration for USB led).
Further I was also able to install VPNC on the router and connect to CISCO VPN without any problem
This makes it easier for me when using Vista x64, as cisco doesn’t provide vpn client for 64bit windows.
Visual Studio 2008 color settings
I recently switched to “dark” vs2008 color scheme since it is easy on eyes.
I started with RagnaRok color scheme created by Tomas Restrepo and modified it to my convenience.
You can download the modified version from here: RagnaRok-modified.
RotateString v2.0
I added the ability to rotate string right or left. Here is the code:
public enum Direction { Right, Left }
string rotateString(string source, int rotationCount, Direction direction)
{
if (String.IsNullOrEmpty(source) || rotationCount <= 0)
return source;
int pivot = 0;
List<int> delimiterLocations = new List<int>();
char[] sourceChars = new char;
char temp;
// Reverse the whole string and
// save the dilimiter locations
for (int i = 0; i < source.Length; i++)
{
sourceChars = source[i];
if (source[i] == ' ')
delimiterLocations.Add(source.Length - i - 1);
}
// calculate neededDelimiters mod wordCount
// (assume words = delimiterCount + 1)
pivot = rotationCount % delimiterLocations.Count;
if (pivot > 0)
{
if (direction == Direction.Left)
pivot = delimiterLocations.Count - pivot;
pivot = delimiterLocations[delimiterLocations.Count - pivot];
}
else
{ return source; }
// reverse the first part
for (int i = 0, j = pivot - 1; i <= j; i++, j--)
{
temp = sourceChars[i];
sourceChars[i] = sourceChars[j];
sourceChars[j] = temp;
}
// reverse the second part
for (int i = pivot + 1, j = sourceChars.Length - 1;
i <= j; i++, j--)
{
temp = sourceChars[i];
sourceChars[i] = sourceChars[j];
sourceChars[j] = temp;
}
return new string(sourceChars);
}
StackOverflow – RotateString
Someone asked the following question on StackOverflow. Before I could post my answer, it was removed. Hmm, so He/She tried to abuse StackOverflow! Anyway, I found that question interesting.
The question was to write a function to rotate a string of words. Words are separated by ‘ ‘ (space). In the result string, the words should remain human readable. The function will also take an integer argument that will specify number of words to rotate.
example:
string source = “A quick brown fox jumps over the lazy dog.”
rotateString(source, 3);
Should give:
the lazy dog. A quick brown fox jumps over
Here is my solution:
static string rotateString(string source, int rotationCount)
{
if (String.IsNullOrEmpty(source) || rotationCount <= 0)
return source;
int pivot = 0;
List<int> delimiterLocations = new List<int>();
char[] sourceChars = new char;
char temp;
// Reverse the whole string and
// save the dilimiter locations
for (int i = 0; i < source.Length; i++)
{
sourceChars = source[i];
if (delimiterLocations.Count < rotationCount && source[i] == ' ')
delimiterLocations.Add(source.Length - i - 1);
}
// calculate neededDelimiters mod wordCount
// (assume words = delimiterCount + 1)
pivot = rotationCount % (delimiterLocations.Count + 1);
if (pivot > 0)
pivot = delimiterLocations[pivot-1];
else
return source;
// reverse the first part
for (int i = 0, j = pivot - 1; i <= j; i++, j--)
{
temp = sourceChars[i];
sourceChars[i] = sourceChars[j];
sourceChars[j] = temp;
}
// reverse the second part
for (int i = pivot + 1, j = sourceChars.Length - 1;
i <= j; i++, j--)
{
temp = sourceChars[i];
sourceChars[i] = sourceChars[j];
sourceChars[j] = temp;
}
return new string(sourceChars);
}
Asus G1S CPU, GPU and Arctic Silver 5
My laptop usually got very hot and CPU and GPU idle temperatures were in the range of ~75C and ~80C respectively. Its about eight months I bought my laptop and I finally decided to open it up and apply thermal paste to both CPU and GPU.
Getting to the CPU was fairly simple just remove 2+4 screws and apply the thermal paste.
But, getting to the GPU was real pain. So, I decided to write a post to describe it in simple steps and gotchas that I discovered.
Disclaimer: Opening and applying thermal paste to CPU or GPU will void your laptop’s warranty! Author assumes absolutely no liability for these opinions nor for any damages that may result from reading and/or implementing following steps.
Before starting anything, unplug the power
and remove the battery.
1. Remove the keyboard. For this remove the two screws that are labeled ‘K’ from the under side of the laptop, then using a small flat(slotted) screw driver, un-clip three plastic holders at the top edge of the keyboard . Then tilt it over and unplug two flat cables and one connector with wires grouped together.
2. Then remove the covers of the ram and the hard drive compartment. Carefully remove the rams modules and hard drive and keep it a side. If you haven’t removed CPU compartment cover do it now.
* In the CPU compartment, there is a screw hidden just between the heat sink exhaust and the edge of the laptop. The is covered by the aluminum foil so you have to peel it to remove it.
* Also don’t forget to remove a screw just beneath the hard drive!
3. Remove 3-4 tiny steel screws from the battery compartment. Then remove the rest of the screws from the laptop base. Also remove six screws from the under side of the hinges. Two hinge covers will come off.
4. Remove screws from backside and remove the lid covering the LCD cables. Carefully remove two screws under this lid. Then unplug the two connectors.
5. Tilt screen to the max and remove two long screws that hold the LCD panel in the grooves. Carefully remove the LCD panel and keep it aside.
6. Now slowly lift the upper cover. It should come off w/o problem. You’ll get a first look at the motherboard. But you still don’t see the GPU!!!
7. You have to remove couple more screws and unplug speaker cables and the DVD drive. And volia! you can lift the motherboard and turn it over. And there you’ll see the other heat sink.
8. Go ahead and remove the screws from the GPU heat sink, unplug the fan power connector and carefully detach the heatsink. You’ll see that the underside of the GPU heat sink has a thin aluminum foil that according to me prevents thermal paste short circuiting the connectors on the GPU. There is another reason for doing it this way. During laptop assembly the person gets hardly few minutes to apply the paste and set the heatsink, it is easy to just use the foil to cover the die and apply paste and slap the sink on to it – quick and dirty huh? But as I told earlier it does prevents thermal paste spreading over the GPU circuitry around the die!
9. Remove the foil and the thermal paste. Observe how excessive paste was used! I used a plastic card to get rid of it and finally cleaned it up with some 90% isopropyl alcohol (picked it from local Wallgreens for $2) and cotton swabs. The copper heat sink was now smooth and clean like a mirror. The die should be clean as new – but just to make sure clean it as well using a new cotton swab and alcohol.
MSI Bootstraper
Msistuff.exe and Setup.exe are two important binaries are needed to create MSI bootstraper. Unfortunately you need to download the whole Windows SDK to access these binaries.
Fortunately there are pre-built msistuff.exe and setup.exe binaries available from WIX examples page:
http://sourceforge.net/tracker/?group_id=105970&atid=654188
Download the Bootstraper example from this page.
Right Click to Open Elevated Command prompt in Vista
To open command prompt with administrator privileges by right clicking on a directory in windows explorer: copy the following text and save it in a file with extension .reg. Then right click it and select merge.
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\shell\runas]
@=”Open Admin Command prompt Here”
[HKEY_CLASSES_ROOT\Directory\shell\runas\command]
@=”cmd.exe /s /k pushd \”%V\”"
Similarly if you need to open Visiual Studio 2008 command prompt with elevated permissions in Vista use this to create the registry file:
Windows Registry Editor Version 5.00
[HKEY_CLASSES_ROOT\Directory\shell\runas]
@=”Elevated VS 2008 Command Prompt”
[HKEY_CLASSES_ROOT\Directory\shell\runas\command]
@=”cmd.exe /s /k pushd \”%V\” && \”C:\Program Files\Microsoft Visual Studio 9.0\Common7\Tools\vsvars32.bat\”"
