Developers
Local Navigation
Mike Kirkup, Research In Motion
With BlackBerry® Device Software v4.0, several classes were exposed which provide the ability for third party applications to add dynamic menu items based on the matching of text in active text fields with string patterns.
Until now, there was no sample provided on how a third party application would be able to leverage this capability in their own application. Possible uses include:
Providing phone number lookup by automatically recognizing phone numbers and looking them up in a reverse phone number lookup database.
Providing additional integration between BlackBerry device applications and third party applications by enabling users to seamlessly transition between applications based on string patterns.
The following sample code highlights a use case of matching zip codes and looking up the zip code to show a list of cities covered by the zip code. For example, when this sample is loaded, a user could type 90210 into an email message and the zip code would be underlined. The user selects the underlined zip code and clicks the trackwheel to bring up the default menu item of "Lookup ZipCode". A zip code look up is performed and the results are displayed in the browser showing Beverly Hills, California as the city covered by that zip code.
There are five classes provided as part of the sample although many of the classes or functionality could be combined into one class. The classes were kept separate to make the sample as easy as possible to understand.
The key to the sample code is the registration of the StringPattern in the StringPatternRepository and the registration of the Factory implementation in the RuntimeStore. These two critical pieces allow the automatic handling of the string pattern matching and dynamic menu item to seamlessly show up for the user.
/*
* PostalCodeLookupSample.java
*
* © Research In Motion Limited, 2006
*/
import net.rim.device.api.system.*;
import net.rim.device.api.util.*;
/**
* This class represents the main class for the application.
* Note that this application has been set up to run on startup
* for initialization purposes and to run as a system module.
* It should not need to be run by the user or show on the ribbon.
*/
public class ZipCodeLookupSample
{
// ZipCodeLookupSample.FACTORY_ID
public static final long FACTORY_ID = 0xe840b1f1c2939e66L;
// ZipCodeLookupSample.INSTANCE_ID
private static final long INSTANCE_ID = 0xa66adeb6a8301cbcL;
// Boolean indicating whether initialization has occured and
// to protect against multiple registration.
private boolean _initialized;
/**
* Entry point for the application.
*/
public static void main( String[] args )
{
ZipCodeLookupSample app = ZipCodeLookupSample.getInstance();
app.initialize();
}
// Mark as private so it cannot be instantiated outside
// this class.
private ZipCodeLookupSample() {}
/**
* Returns the one instance of the ZipCodeLookupSample
* on the system.
*/
public static ZipCodeLookupSample getInstance()
{
RuntimeStore rs = RuntimeStore.getRuntimeStore();
ZipCodeLookupSample sample =
(ZipCodeLookupSample)rs.get( INSTANCE_ID );
if( sample == null ) {
sample = new ZipCodeLookupSample();
rs.put( INSTANCE_ID, sample );
}
return sample;
}
/**
* The initialize method will register the application
* with all of the appropriate factories. It is important
* to note that this method can be invoked multiple times
* but it will only initialize the structures once.
*/
public void initialize()
{
if( !_initialized ) {
ZipCodeLookupFactory factory = new ZipCodeLookupFactory();
RuntimeStore.getRuntimeStore().put( FACTORY_ID, factory );
ZipCodeLookupStringPattern pattern =
new ZipCodeLookupStringPattern();
StringPatternRepository.addPattern( pattern );
_initialized = true;
}
}
}
/*
* ZipCodeLookupFactory.java
*
* © Research In Motion Limited, 2006
*/
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.util.*;
/**
* The ZipCodeLookupFactory provides the necessary piece
* which allows the StringPattern.Match class to indicate
* how it should move forward when a match is found.
* One will note that the ID for the Factory is the same
* ID that is passed back from the findMatch method in the
* StringPattern.
*/
public class ZipCodeLookupFactory implements Factory
{
public ZipCodeLookupFactory()
{
// Do nothing for now
}
/**
* Create a new instance of the ActiveFieldCookie
* using the information passed into this method.
* @param initialData An ActiveFieldContext that contains
* the necessary information from the StringPattern class.
* @return An ActiveFieldCookie embodying the matched pattern.
*/
public Object createInstance( Object initialData )
{
if ( initialData instanceof ActiveFieldContext ) {
ActiveFieldContext afc = (ActiveFieldContext)initialData;
String stringData = (String)afc.getData();
return new ZipCodeLookupActiveFieldCookie( stringData );
}
return null;
}
}
/*
* ZipCodeLookupMenuItem.java
*
* © Research In Motion Limited, 2006
*/
import java.io.*;
import javax.microedition.io.*;
import net.rim.blackberry.api.browser.*;
import net.rim.device.api.io.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.util.*;
/**
* The ZipCodeLookupMenuItem class provides the MenuItem
* implementation that is displayed in the appropriate application
* when the pattern has been found. In this particular
* implementation, the MenuItem will send the ZipCode to a website
* asking for the appropriate cities in that ZipCode. The results
* are displayed in the browser.
*/
public class ZipCodeLookupMenuItem extends MenuItem
{
// ZipCodeLookupMenuItem.GUID
private static final long GUID = 0x663468deaaa15552L;
private String URL =
"http://www.getzips.com/CGI-BIN/ziplook.exe?What=1&Zip=";
private String DEVICE_SIDE = ";deviceside=true";
private String _zipCode;
/**
* Create the ZipCodeLookupMenuItem
* @param zipCode the zip code to lookup
*/
public ZipCodeLookupMenuItem( String zipCode )
{
super( "Lookup ZipCode", 5, 5 );
_zipCode = zipCode;
}
/**
* In the run method, simply start the HTTP thread that will
* lookup the zip code.
*/
public void run()
{
ZipCodeLookupThread thread = new ZipCodeLookupThread();
thread.start();
}
/**
* The ZipCodeLookupThread will open up the HTTP Connection and
* communicate with the server to obtain the information from
* looking up the zipcode. After capturing all of the
* information, it will be passed into the browser in such a way
* that doesn't require the browser to connect to the network.
*/
private class ZipCodeLookupThread extends Thread
{
public void run()
{
DataBuffer dbuffer = new DataBuffer();
try {
// Use a StringBuffer to piece together the URL.
StringBuffer sbuffer = new StringBuffer();
sbuffer.append( URL ).append( _zipCode ).
append( DEVICE_SIDE );
HttpConnection connection =
(HttpConnection)Connector.open( sbuffer.toString() );
InputStream input = connection.openInputStream();
// Read in the data from the InputStream
byte[] temp = new byte[ 1024 ];
for( ;; ) {
int bytesRead = input.read( temp );
if( bytesRead == -1 ) {
break;
}
dbuffer.write( temp, 0, bytesRead );
}
input.close();
connection.close();
} catch( IOException e ) {
EventLogger.logEvent( GUID, e.toString().getBytes() );
}
try {
// Create the ByteArrayOutputStream that will be used to
// capture the Base64 encoded data.
ByteArrayOutputStream output =
new ByteArrayOutputStream();
Base64OutputStream boutput =
new Base64OutputStream( output );
output.write( "data:text/html;base64,".getBytes() );
boutput.write( dbuffer.getArray() );
boutput.flush();
boutput.close();
output.flush();
output.close();
// Launch the browser using the base64 encoded data above.
BrowserSession bSession = Browser.getDefaultSession();
String data = output.toString();
bSession.displayPage( data );
} catch( IOException e ) {
EventLogger.logEvent( GUID, e.toString().getBytes() );
}
}
}
}
/*
* ZipCodeLookupStringPattern.java
*
* © Research In Motion Limited, 2006
*/
import net.rim.device.api.util.*;
/**
* This class provides an implementation of the StringPattern.
* It is important to note that every single time a character
* is entered into an active field that this method will be
* executed. As such, it is incredibly important to stress that
* the findMatch method must be meticulously written for speed
* and efficiency so as to not alter the user experience.
*/
public class ZipCodeLookupStringPattern extends StringPattern
{
public ZipCodeLookupStringPattern() {}
/**
* For the purposes of this implementation, findMatch will match
* any five digit continuous number (no spaces, etc) that it
* finds. It is better to err on the side of caution and accept
* a zip code than exclude it.
* @param str the AbstractString to search through.
* @param beginIndex the beginning index in the string.
* @param maxIndex the ending index in the string.
* @param match the holder for information on the match
* if applicable
* @return true if a match was found and false otherwise
*/
public boolean findMatch
( AbstractString str, int beginIndex, int maxIndex,
StringPattern.Match match )
{
// Because we are matching against a zip code which is a minimum
// of 5 numbers, we can perform a quick check here to ensure
// that it is worth evaluating the actual characters.
if( maxIndex - beginIndex < 5 ) {
return false;
}
int numCounter = 0;
for( int i = beginIndex; i < maxIndex; i++ ) {
if( CharacterUtilities.isDigit( str.charAt( i ))) {
numCounter++;
if( numCounter == 5 ) {
// Begin Index is zero based
match.beginIndex = i - 4;
// End Index is expecting the end index after the
// last matching character
match.endIndex = i + 1;
match.id = ZipCodeLookupSample.FACTORY_ID;
match.prefixLength = 0;
return true;
}
} else {
numCounter = 0;
}
}
return false;
}
}
/*
* ZipCodeLookupActiveFieldCookie.java
*
* © Research In Motion Limited, 2006
*/
import java.util.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
/**
* The ActiveFieldCookie implementation allows for the factory to
* provide a container for the zipcode information when moving
* between the StringPattern and the MenuItem.
*/
public class ZipCodeLookupActiveFieldCookie
implements ActiveFieldCookie
{
// The ZipCode that matched the pattern.
private String _zipCode;
/**
* Create the ZipCodeLookupActiveFieldCookie by passing in
* the zip code
* @param data the zip code to lookup
*/
public ZipCodeLookupActiveFieldCookie( String data )
{
// Save the zipcode for later use.
_zipCode = data;
}
/**
* This is an abstract method for the ActiveFieldCookie
* and must be implemented.
* @return always return false.
*/
public boolean invokeApplicationKeyVerb()
{
return false;
}
/**
* In this case, we are under-utilizing the getFocusVerbs method
* by simply adding our menu item to the list of items and
* returning.
* Refer to the javadocs for all of the different options here.
*/
public MenuItem getFocusVerbs(CookieProvider provider,
Object context, Vector items)
{
items.addElement( new ZipCodeLookupMenuItem( _zipCode ) );
return (MenuItem)items.elementAt(0);
}
}
Please email your comments, suggestions and editorial submissions to