The MIDP 2.0 Push Registry

   

"Push" is a very powerful concept and typically refers to the mechanism or ability to receive and act on information asynchronously, as information becomes available, instead of forcing the application to use synchronous polling techniques that increase resource use or latency.

This article will cover the new mechanism called the push registry, introduced in MIDP 2.0 (JSR 118). You'll start by finding out what the push registry comprises, see an overall description of its exposed API, and learn how to use this API to push-enable your application. The article will end by covering use of the version of the J2ME Wireless Toolkit that supports MIDP 2.0 to test your push-enabled MIDP application.

The Push Registry

The push registry enables MIDlets to set themselves up to be launched automatically, without user initiation. The push registry manages network- and timer-initiated MIDlet activation; that is, it enables an inbound network connection or a timer-based alarm to wake a MIDlet up. For example, you can write a workgroup application that employs network activation to wake up and process newly received email, or new appointments that have been scheduled. Or you can use timer-based activation to schedule your MIDlet to synchronize with a server every so often then go to sleep.

The push registry is part of the application management system (AMS), the software in the device that's responsible for each application's life-cycle (installation, activation, execution, and removal). The push registry is the component of the AMS that exposes the push API and keeps track of push registrations. Figure 1 summarizes the elements of the push registry.

fig 1
Figure 1: Typical Elements of the Push Registry

The PushRegistry API allows you to register push alarms and connections, and to retrieve information about push connections. A typical push registry maintains lists of connection and alarm registrations in both memory and persistent storage.

The PushRegistry API

The push registry is part of the Generic Connection Framework (GCF) and is encapsulated within a single class, javax.microedition.io.PushRegistry, which exposes all the push-related methods. Table 1 describes the PushRegistry class.

Table 1. Class javax.microedition.io.PushRegistry

Class definition: public class Pushregistry extends Object

Method

Description

getFilter()

Returns the filter < Allowed-Sender> for the specified push connection

getMidlet()

Returns the Midlet responsible for handling the specified push connection

listConnections()

Returns the list of registered push connections for the Midlet suite

registerAlarm()

Registers a timer-based alarm to launch the Midlet. Disables alarms if an argument of zero is passed

registerConnection()

Registers a push connection

unregisterConnection()

Unregisters a push connection


With the PushRegistry API you can register a MIDlet for push events (inbound network connections and time-based alarms), discover whether the MIDlet was activated by an inbound connection, and retrieve push-specific information for a particular connection.

Please refer to the MIDP 2.0 push registry specification and Javadoc for detailed information.

Exceptions to Handle

When using the PushRegistry API you may encounter some exceptions you should handle. For example, you may get an exception if the platform doesn't support a particular connection type, or doesn't have sufficient resources. Table 2 describes the exceptions PushRegistry may throw:

Table 2 PushRegistry Exceptions

Exception

Thrown by

Description

ClassNotFoundException

registerConnection()

registerAlarm()

The specified MIDlet class name can’t be found in the current MIDlet suite. In other words, the specified MIDlet class name was not defined (MIDlet-<n> attribute) in the descriptor file orthe JAR file manifest, or the MIDlet argument is null.

ConnectionNotFoundException

registerConnection()

 

The platform does not support the specified connection type for push inbound connections.

registerAlarm()

The platform does not support alarm-based application launch.

Connector.open()

The requested protocol does not exist, or the connection could not be made.

IllegalArgumentException

registerConnection()

The connection string is not valid, or the filter string is not valid.

Connector.open()

One of the arguments is invalid.

IOException

registerConnection()

The connection is already registered, or there are insufficient resources to handle the registration.

Connector.open()

A generic I/O error was encountered.

SecurityException

registerConnection()

 

The specified MIDlet does not have permission to register a connection.

unregisterConnection()

The specified connection was registered by another MIDlet suite.

registerAlarm()

The specified MIDlet does not have permission to register an alarm.

Connector.open()

The MIDlet has no permission to use the requested protocol.


Security exceptions are a new feature. MIDP 2.0 applications must request permissions before using privileged operations such as network connections or the push registry. If you fail to request proper permissions the platform may throw a SecurityException. Make sure you trap these appropriately.

MIDlet Activation and Life-Cycle

The advent of the push registry doesn't change the MIDlet life-cycle, but it does introduce two new ways a MIDlet may be activated:

  • By inbound network connections
  • By timer-based alarms

Figure 2 summarizes the MIDlet life-cycle and activation methods.

fig 2
Figure 2: MIDP 2.0 MIDlet Life-Cycle and Activation

Which activation method is used at any given time depends on the push registration method used, as you'll see shortly.

Sharing the Responsibility for Push

In MIDP 2.0 the responsibility for push is shared between the MIDlet and the AMS. Once a MIDlet has registered itself with the push registry, the responsibility for push processing is split as follows:

  1. When the MIDlet is not active , the AMS monitors registered push events on behalf of the MIDlet. When a push event occurs, the AMS activates the appropriate MIDlet to handle it. Figure 3 illustrates this sequence for a network activation:
    fig 3
    Figure 3: Network Activation Sequence Diagram
  2. If the MIDlet is active (running) , the MIDlet itself is responsible for all push events. It must set up and monitor inbound connections, and schedule any cyclic tasks it needs - basically standard networking and timer processing.

This separation of responsibilities not only simplifies the AMS implementation but also avoids imposing a format or method on how information is pushed down to the device - these decisions are left up to the application designers.

About Inbound Connections

To support network-based push activation, the platform must support inbound connection types. In MIDP 1.0 the only officially defined connection type was HttpConnection - a client type of network connection and thus not suitable for push by inbound connections. MIDP 2.0 defines new connection types beyond HTTP, including TCP sockets and UDP datagrams, that can be set up as inbound connections, making them suitable for push. In addition, the Wireless Messaging API (WMA, JSR 120) makes SMS-based push activation possible as well. The MIDP 2.0 specification doesn't mandate which inbound connection types must be available for push; that decision is left up to the platform vendors. Typically, though, we will have socket and datagrams available to us. Note that requesting an unsupported connection type will result in a ConnectionNotFoundException.

Inbound connections can be message-based (such as SMS), stream-based (such as TCP socket) or packet-based (such as datagrams). Some of these connections may buffer incoming data before MIDlet activation, as SMS does. Other types, such as sockets or datagrams, do no buffering; the MIDlet is activated by an incoming connection. The MIDlet must open the connection promptly, or a timeout may occur - but note that this is typical network processing and not peculiar to push registry.

An inbound network connection is based on either a static or a dynamic local address. A local address typically consists of two parts, an address, such as an IP address or in the case of SMS a phone number, and a port identifier. Phone numbers are static (fixed) in nature, while IP addresses are either static or dynamically assigned. To receive a static IP address, you typically need to make special arrangements with your network provider. Ports may also be static or dynamically assigned. A static port (also called a well known port) is typically assigned by the Internet Assigned Numbers Authority ( IANA). To get your application an IANA-assigned port, you must go through a formal request process in which you must explain why and how the port will be used; IANA may or may not honor your request for a static port. Because static ports are not applicable to all cases, systems commonly use dynamic addresses, assigned at runtime.

To create an inbound connection based on a static address, call Connector.open() with a URL that describes both a protocol and a local inbound port; for example:

Connector.open("socket://:5000")
Connector.open("datagram://:5000")
Connector.open("sms://:5000")

To create an inbound connection based on a dynamic address, call Connector.open() with a URL that describes just a protocol (and not a local inbound port), to indicate you want the system to assign an address; for example:

Connector.open("socket://")
Connector.open("datagram://")

When using a system-assigned address, you must publish this address so that external systems can connect to your application. If you're using a ServerSocketConnection or a UDPDatagramConnection you can get the dynamically assigned address from the getLocalAddress() and getLocalPort() methods. You can also retrieve the hostname assigned to your device, by invoking System.getProperty("microedition.hostname"). To publish the dynamic address on an external system you can simply use HTTP.

Push Registration

To become push-enabled, MIDlets must register with the push registry, using one of two types of registration:

  • Static Registration - Registrations of static (well known) connections occur during the installation of the MIDlet suite. You specify them by listing MIDlet-Push attributes in the MIDlet suite's JAD file or JAR manifest. The installation will fail if you attempt to register an address that's already bound. Uninstalling a MIDlet suite automatically unregisters the connection.
  • Dynamic Registration - You register dynamic connections and timer alarms at runtime, using the PushRegistry API.

In some cases you may want to register a static connection conditionally, or to ensure that push exceptions will not preclude your MIDlet suite from installing. In such cases you can use the PushRegistry API to register your static connection and catch any IOExceptions or SecurityExceptions thrown. Typically, however, you'll register a static connection using the JAD file, and let the system unregister it when the MIDlet suite is uninstalled.

Note that, while you can use either method to register inbound connections, timer-based activation can be registered only at runtime.

Static Registration - Using the MIDlet-Push Attribute

Static registrations are defined by listing one or more MIDlet-Push attributes in the JAD file or JAR manifest. The AMS performs static registration when the MIDlet suite is installed. Similarly, when the MIDlet suite is uninstalled, the AMS automatically unregisters all its associated push registrations.

The format of the MIDlet-Push attribute is...

MIDlet-Push-<n>: <ConnectionURL>, <MIDletClassName>,
    <AllowedSender>

...where:

  • MIDlet-Push-< n> is the property name that identifies push registration, and where < n> is a number starting from 1; for example, MIDlet-Push-1. Note that multiple push entries are allowed.
  • < ConnectionURL> is a URL connection string that identifies the inbound endpoint to register, in the same URL format used when invoking Connector.open(). For example, socket://:5000 reserves an inbound server socket connection on port 5000.
  • < MIDletClassName> is the fully qualified class name of the MIDlet to be activated when network activity in < ConnectionURL> is detected; for example, j2medeveloper.basicpush.PushMIDlet.
  • < Allowed-Sender> is a filter used to restrict the servers that can activate < MIDletClassName> . You can use wildcards; a * indicates one or more characters and a ? indicates one character. For example, 192.168.1.190, or 192.168.1. * , or 192.168.19 ?.1 , or simply * .

Listing 1 shows a JAD file with a static registration of an inbound socket connection on port 5000, with no address filtering, which activates MIDlet j2medeveloper.basicpush.PushMIDlet.

MIDlet-1: PushMIDlet,,j2medeveloper.basicpush.PushMIDlet
MIDlet-2: WMAMIDlet,,j2medeveloper.wma.WMAMIDlet
MIDlet-Name: MyMIDletSuite
MIDlet-Vendor: Sun Microsystems, Inc.
MIDlet-Version: 1.0
MIDlet-Jar-Size: 4735
MIDlet-Jar-URL: basicpush.jar
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-1.0
MIDlet-Push-1: socket://:5000,
    j2medeveloper.basicpush.PushMIDlet, *
MIDlet-Permissions: javax.microedition.io.PushRegistry,
    javax.microedition.io.Connector.serversocket

Listing 1. JAD File with a MIDlet-Push Attribute

If the requested local address < ConnectionURL> is already in use, the installation will fail; how this failure is presented to the user depends on the vendor and the implementation.

Note the MIDlet-Permissions property, which is used to request permissions for the MIDlet suite. This example requests permission to use the push registry and socket APIs. The "Security Considerations" section will cover permissions in more detail.

Dynamic Registration

You'll use the PushRegistry API to accomplish dynamic registration of both inbound network connections and timer-based alarms, at runtime. Let's look at each push type individually.

Registering a Timer Alarm

Your MIDlet may be required to perform some cyclic processing every so often. For example, it may need to synchronize with a server every 30 minutes. Your MIDlet will typically use a TimerTask to schedule a thread for cyclic processing, as illustrated in Listing 2:

...

// cyclic background task info.
long REFRESH_TIME = 1000*60*5; // every 5 minutes
Timer aTimer = new Timer();
MyTask myTask = new MyTask();
aTimer.schedule(myTask, 0, REFRESH_TIME);

...

class MyTask extends TimerTask {
    // Constructor.
    public MyTask() {
    }

    ...

    // Thread run method.
    public void run() {
        ... Your Task Logic
    }
}

...

Listing 2. Using TimerTask for Cyclic Processing

This example schedules the class MyTask for execution every REFRESH_TIME milliseconds. You put the logic of the required cyclic processing in the run() method, the entry point to your thread.

If your MIDlet needs to continue its cyclic processing even when it's not running, you can use push alarms to schedule your MIDlet for future execution. To schedule a MIDlet launch by the AMS the MIDlet invokes the PushRegistry.registerAlarm() method, passing as arguments the fully qualified class name of the MIDlet to launch, and the time for the launch. Passing a time of zero disables the alarm. Note that only one outstanding alarm per MIDlet is supported, and invoking this method overwrites any previously scheduled alarm.

In this code sample, a helper method uses the registerAlarm() method to schedule the launch of the currently executing MIDlet:

/**
 *  Schedule this MIDlet's launch.
 *  @param deltatime the length of time in
 *  milliseconds before expiration.
 */
private void scheduleMIDlet(long deltatime)
    throws ClassNotFoundException,
           ConnectionNotFoundException,
           SecurityException {

    String cn = this.getClass().getName();
    // Get the current time by calling Date.getTime()
    Date alarm = new Date();
    long t = PushRegistry.registerAlarm(cn,
         alarm.getTime()+deltatime);
}

Listing 3. Using the registerAlarm() Method

If the call to registerAlarm()overwrites a previous timer, it returns that timer's scheduled time; if not, it returns 0.

Note that the PushRegistry API doesn't provide a way to discover whether the MIDlet was activated by an alarm. If that knowledge is important, you must implement your own detection method. One way is to save information, such as the time of launch, in the RMS, then compare the saved time with the current time when the MIDlet is launched.

A MIDlet that requires push alarms must schedule them before it exits. The following code snippet uses the scheduleMIDlet() method on Listing 3 to schedule the MIDlet's launch before exiting:

/**
 *  Destroyed state. Release resources (connection,
 *  threads, etc). Also, schedule the future launch of
 *  the MIDlet. @param uc If true when this method is
 *  called, the MIDlet must cleanup and release all
 *  resources. If false the MIDlet may throw
 *  a MIDletStateChangeException to indicate it does
 *  not want to be destroyed at this time.
 *  @throw MIDletStateChangeException to indicate
 *  it does not want to be destroyed at this time.
 */
public void destroyApp(boolean uc) throws
    MIDletStateChangeException {
    // Release resources
    ...
    //  Set up the alarm and force the MIDlet to exit.
    scheduleMIDlet(defaultDeltaTime);
    display = null;
}

Listing 4. Scheduling Alarm-Based Activation Before Exiting

When destroyApp() is invoked, it does its typical cleanup, then schedules the push alarm before exiting.

Registering an Inbound Connection

Now let's look at some examples that show how to register an inbound connection, starting with one that uses a user-defined local port. The MIDlet calls registerConnection() to register the newly created inbound (server) socket connection:

...

//  MIDlet class name.
String midletClassName = this.getClass().getName();
//  Register a static connection.
String url = "socket://:5000";
//  Use an unrestricted filter.
String filter = "*";

try {
    // Open the connection.
    ServerSocketConnection ssc =
        (ServerSocketConnection)Connector.open(url);
    // Register the connection now so that when this
    // MIDlet exits (is destroyed), the AMS can activate
    // our MIDlet when network activity is detected.
    // The AMS will remember the registered URL
    // even when the MIDlet is not active.
    PushRegistry.registerConnection(url, midletClassName, filter);
    // Now wait for inbound network activity.
    SocketConnection sc =
    (SocketConnection)ssc.acceptAndOpen();
    // Read data from inbound connection.
    InputStream is = sc.openInputStream();
    //  ..... read from the input stream.

    // Here process the inbound data.
    //  .....
}
catch(SecurityException e) {
    System.out.println("SecurityException,
        insufficient permissions");
    e.printStackTrace();
}
catch(ClassNotFoundException e) {
    System.out.println("ClassNotFoundException,
    MIDlet name not found");
    e.printStackTrace();
}
catch(IOException e) {
    System.out.println("IOException,
        possibly port already in use.");
    e.printStackTrace();
}

...

Listing 5. Registering a User-Defined Server Socket Connection

Registering an inbound datagram connection is done much the same way. You just use a DatagramConnection and a Datagram instead:

...

//  MIDlet class name.
String midletClassName  = this.getClass().getName();
//  Register a static connection.
String url = "datagram://:5000";
//  Use an unrestricted filter.
String filter = "*";

try {
    // Open the connection.
    UPDDatagramConnection udgc =
    (UDPDatagramConnection)Connector.open(url);
    // Register the connection now so that when this
    // MIDlet exits (is destroyed), the AMS can activate
    // our MIDlet when network activity is detected. The
    // AMS will remember the registered URL even
    // when the MIDlet is not active.
    PushRegistry.registerConnection(url, midletClassName,
        filter);
    // Now wait for inbound network activity.
    Datagram dg = udgc.newDatagram(udgc.getNomialLength());
    udgc.receive(dg);
    // Read the inbound messages
    while (!done) {
        udgc.receive(dg);
        //--------------------------------------+
        //  Here do something with received data|
        //--------------------------------------+
        System.out.print(new String(dg.getData()));
    }
}
catch(SecurityException e) {
    System.out.println("SecurityException,
        insufficient permissions");
    e.printStackTrace();
}
catch(ClassNotFoundException e) {
    System.out.println("ClassNotFoundException,
        MIDlet name not found");
    e.printStackTrace();
}
catch(IOException e) {
    System.out.println("IOException, possibly
        port already in use.");
    e.printStackTrace();
}

...

Listing 6. Registering a User-Defined UDP Datagram Connection

These two examples registered a user-defined port. Let's look at an example where you let the system allocate the port for you. Because the port is system-assigned, the MIDlet must discover the assigned port and publish it:

...

//  MIDlet class name.
String midletClassName  = this.getClass().getName();
//  Register a dynamic connection.
String url = "socket://";
//  Use an unrestricted filter.
String filter = "*";

try {
    // Open the connection.
    ServerSocketConnection ssc =
        (ServerSocketConnection)Connector.open(url);
    // Discover the system-assigned port.
    url = "socket://:" + ssc.getLocalPort();
    // Register the connection now. The AMS will remember
    // the registered URL even when the MIDlet is not active.
    PushRegistry.registerConnection(url, midletClassName, filter);
    // Now publish the push URL. We can use an HTTP
    // POST or a socket or datagram for this.
    String purl;
    purl = "socket://"+ssc.getLocalAddress()+":
        "+ssc.getLocalPort();
    publishInboundConnection(purl, midletClassName);
}
catch(SecurityException e) {
    System.out.println("SecurityException,
        insufficient permissions");
    e.printStackTrace();
}
catch(ClassNotFoundException e) {
    System.out.println("ClassNotFoundException,
        MIDlet name not found");
    e.printStackTrace();
}
catch(IOException e) {
    System.out.println("IOException, possibly
        port already in use.");
    e.printStackTrace();
}

...

Listing 7. Registering and Publishing a System-Assigned Server Socket Connection

To use an HTTP connection to publish this MIDlet's URL to an external agent, the code in Listing 7 invokes publishInboundConnection(), defined in Listing 8:

/**
*  An example of a method to publish dynamic inbound
*  address.
*  @param url is the inbound address
*  @param midlet is the name of the midlet
*/
private void publishInboundConnection(String url,
    String midlet) {
    String hostToPostTo = "j2medeveloper:8080/pushagent";
    OutputStream os = null;
    HttpConnection hc = null;
    try {
        if (hostToPostTo.startsWith("http://")
            == false) {
            hostToPostTo = "http://" + hostToPostTo;
        }
    hc = (HttpConnection)Connector.open(hostToPostTo);
    //  Set HTTP request method, set Content-Type,
    //  send body.
    int len = url.length() + midlet.length();
    String pushinfo = "pushurl=" + url +
    ", pushmidlet=" + midlet ;
    hc.setRequestMethod(HttpConnection.POST);
    hc.setRequestProperty("User-Agent",
        "Profile/MIDP-2.0 Configuration/CLDC-1.0" );
    hc.setRequestProperty("Content-Language",
        "en-US");
    hc.setRequestProperty("Accept",
        "text/xml");
    hc.setRequestProperty("Connection",
        "close");
    hc.setRequestProperty("Content-Type",
        "application/x-www-form-urlencoded");
    hc.setRequestProperty ("Content-Length",
        ""+pushinfo.length());
    os = hc.openOutputStream();
    os.write(pushinfo.getBytes());
}
    catch(Exception e) {
        e.printStackTrace();
    }
    finally {
        try {
            if (os != null)
                os.close();
            if (hc != null)
                hc.close();
        }
        catch (IOException e) {    
            e.printStackTrace();
        }
    }
}

Listing 8. Example of How to Publish the Push URL Using HTTP

Output of publishInboundConnection():

POST /pushagent HTTP/1.1
User-Agent: Profile/MIDP-2.0 Configuration/CLDC-1.0
Content-Language: en-US
Accept: text/xml
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 91
Host: j2medeveloper:8080

pushurl=socket//:8354,
pushmidlet=j2medeveloper.basicpush.BasicPushMIDlet

To use datagrams instead of a server socket, simply substitute a UDPDatagramConnection:

...

//  MIDlet class name.
String midletClassName  = this.getClass().getName();
//  Register a dynamic connection.
String url = "datagram://";
//  Use an unrestricted filter.
String filter = "*";

try {
    // Open the connection.
    UDPDatagramConnection udgc =
        (UDPDatagramConnection)Connector.open(url);
    // Discover the system-assigned port.
    url = "datagram://:" + udgc.getLocalPort();
    // Register the connection now. The AMS will remember the
    // registered URL even when the MIDlet is not active.
    PushRegistry.registerConnection(url, midletClassName,
        filter);
    // Now publish the push URL. We can use an HTTP POST or
    // a socket or datagram for this.
    String purl;
    purl = "datagram://"+udgc.getLocalAddress()+
        ":"+udgc.getLocalPort();
    publishInboundConnection(purl, midletClassName);
}
catch(SecurityException e) {
    System.out.println("SecurityException, insufficient
        permissions");
    e.printStackTrace();
}
catch(ClassNotFoundException e) {
    System.out.println("ClassNotFoundException,
        MIDlet name not found");
    e.printStackTrace();
}
catch(IOException e) {
    System.out.println("IOException, possibly port
        already in use.");
    e.printStackTrace();
}

...

Listing 9. Registering and Publishing a System-Assigned Connection

Once the connection is registered, the AMS will remember the requested address and port, and keep them reserved even after the MIDlet is destroyed so it can fulfill its responsibility to monitor push activity and reactivate the MIDlet.

Unregistering an Inbound Connection

Because the AMS maintains the registration even after the MIDlet exits, it's important that the MIDlet unregister the connection when it's no longer needed. To unregister an inbound connection, use the unregisterConnection() method:

...

try {
    boolean status;
    // unregisterConnection returns false if it was
    // unsuccessful and true if successful.
    status = PushRegistry.unregisterConnection(url);
}
catch(SecurityException e) {
    System.out.println("SecurityException,
        insufficient permissions");
    e.printStackTrace();
}

...

Listing 10. Unregistering a Connection

The method returns true if it was successful and false if it fails to unregister the connection, for example if the argument is null, or it specifies a connection that hasn't been registered. The method throws SecurityException if the specified MIDlet has been registered by another MIDlet suite.

Discovering Whether a MIDlet Was Push-Activated

The PushRegistry.listConnections() method allows you to discover all the inbound connections registered by the MIDlet suite. You can also use it to discover whether the MIDlet was activated by an incoming connection.

If you pass false to listConnections(), the method returns a string array that identifies all the inbound connections registered by the MIDlet suite, but if you pass a true argument the method reports only the registered connections with available data - indicating that MIDlet activation was due to incoming network data . Let's look at a method that uses listConnections() to discover whether the MIDlet was push-activated, and that processes each push connection individually.

/**
*  Determine if activated due to inbound connection and
*  if so dispatch a PushProcessor to handle incoming
*  connection(s). @return true if MIDlet was activated
*  due to inbound connection, false otherwise
*/
private boolean handlePushActivation() {
    //  Discover if there are pending push inbound
    //  connections and if so, dispatch a
    //  PushProcessor for each one.
    String[] connections =
        PushRegistry.listConnections(true);
    if (connections != null && connections.length > 0) {
        for (int i=0; i < connections.length; i++) {
            PushProcessor pp = new
                PushProcessor(connections[i]);
        }
        return(true);
    }
    return(false);
}

Listing 11. Using the listConnections() Method

For each inbound connection returned, handlePushActivation() dispatches a PushProcessor that opens the connection, and waits for and consumes incoming data.

Getting Information About a Push Connection

The push registry provides two other methods to retrieve information about a registered connection - specifically, the information defined by the MIDlet-Push property or by a call to registerConnection():

  • getMIDlet() retrieves the MIDlet responsible for a particular connection.
  • getFilter() retrieves the <Allowed-Sender> filter for a particular connection.

The following code uses these two methods:

/**
* Output push info for all push connections for the MIDlet suite
*/
private void outputPushInfo() {
    //  Discover if there are pending push inbound
    //  connections and if so, output the push info
    //  for each one.
    String[] connections =
        PushRegistry.listConnections(false);
    if (connections != null &&
            connections.length > 0) {
        for (int i=0; i < connections.length; i++) {
            String midlet =
                PushRegistry.getMIDlet(connections[i]);
            String filter =
                PushRegistry.getFilter(connections[i]);
            // Output the info.
            System.out.println("PushInfo -> " +
            connections[i] + " " + midlet +
                " " + filter);
            form.append(connections[i] + " " +
                midlet + " " + filter);
        }
    }
}

Listing 12. Using the getMIDlet() and getFilter()Methods

This method calls listConnection() with a false argument because it needs to report on all connections registered for the MIDlet suite. Then for each connection it retrieves and reports the associated MIDlet and filter information.

Security Considerations

MIDP 2.0 introduces two highly useful security concepts, application signing and privileged domains. Application signing enables the platform to determine whether to trust another application, basing the decision on that application's origin and integrity, and on the use of an X.509 digital certificate. Privileged domains allow the platform to use a policy definition to restrict access to APIs. Policy definitions are transparent to the MIDlet and are typically owned by the service provider, in the case of a phone, the wireless carrier, for example.

Network and push operations are considered restricted: applications must request permission before using them. Attempting to use a restricted operation without proper permission causes the system to throw a SecurityException.

You request permissions in the JAD file or the JAR manifest, by creating MIDlet-Permissions property entries. Permission names follow the hierarchical naming convention used in Java packages. For example, to request permission to use the PushRegistry and ServerSocketConnection APIs, define the following property entry:

MIDlet-Permissions: javax.microedition.io.PushRegistry,
javax.microedition.io.Connector.serversocket

Listing 13. The MIDlet-Permissions Property

Refer to Table 2 for more information on SecurityException.

Other Important Considerations

There are other push-related considerations worth mentioning:

  • PushRegistry and WMA - While MIDP 2.0 defines socket and datagram connection types, it does not define message-oriented connection types, such as SMS. The Wireless Messaging API introduces MessageConnection, a special connection type for message-oriented connections. You can set up a MessageConnection for inbound connections and use it for push. Refer to the WMA specification and recommended practices for more information, for example on how to specify WMA open, send, and receive permissions in MIDP 2.0 applications.
  • User interface and headless applications - Even though it's possible to create a push-activated MIDlet that exposes no user interface, doing so may confuse the user - at a minimum, provide a simple screen that tells the user what your application is attempting to do.

Using the Wireless Toolkit Emulator

The J2ME Wireless Toolkit, version 2.0, includes an implementation of MIDP 2.0. With it you can test your push registry alarms and inbound socket and datagrams connections.

Once you have developed your push-enabled MIDlet, you can install it in the device emulator for testing. Note that for static push registrations to work properly you must install the application using the AMS, which in the toolkit is the Java Application Manager (JAM) - in other words, running the MIDlet from the command line is not sufficient.

One way to start MIDlet installation is to invoke the JAM on the command line:

emulator -Xjam

If you prefer to use Ant, you can create a target like this one:

<!-- ============== -->
<!-- Invoke the JAM -->
<!-- ============== -->
<property name="wtkbase"
    value="C:\WTK20"/>
<target name="jam">
    <exec executable="${wtkbase}\bin\emulator">
        <arg line="-Xjam"/>
    </exec>
</target>

This approach assumes of course that you have installed Ant. To execute the JAM target, invoke Ant from the command line:

ant jam

Listing 14. Invoking the JAM

Either way, you'll see the AMS GUI start, as in Figure 4a. Select Apps, and from the menu select Install Application (4b), which will bring up the "Enter a website to Install From" page (4c). Enter the appropriate URL, select Menu, and select Go (4d).

fig-4a

fig-4b

(4a)

(4b)

fig-4c

fig-4d

(4c)

(4d)


Figure 4. Installing Your MIDlet Using the JAM

The AMS contacts the specified website using the same protocol used for over-the-air (OTA) provisioning, described in the article Introduction to OTA Application Provisioning. The AMS retrieves and presents a menu of applications available for installation (5a). Select the one of interest and press Install. The AMS downloads the JAD file if one is specified and prompts you for confirmation (5b). Press Install to initiate the installation.

fig-5a

fig-5b

(5a)

(5b)

Figure 5. Selecting the MIDlet to Install

Because security is so important in MIDP 2.0, you encounter a series of authorization prompts when the MIDlet is installed and activated. If the application registers push connections statically, the installation process prompts you for authorization as illustrated in Figure 6a and 6b - this behavior is controlled via the policy definition. When an incoming connection is received or an alarm expires, the AMS prompts you again before activating the MIDlet, as illustrated in 6c. You can select "Yes, always. Don't ask again" if you don't want to be prompted again about the particular operation. You encounter similar prompts when attempting to send.

fig-6a

fig-6b

fig-6c

(6a)

(6b)

(6c)


Figure 6. MIDlet User Authorization

Once authorized, the MIDlet is ready for activation.

Summary

This article has covered the MIDP 2.0 push registry. The push registry implements a MIDlet-activation model that allows MIDlets to become active as a result of network activity or timer alarms. To become push-enabled, a MIDlet must register with the push registry. The push registry supports static and dynamic push registrations. Static registrations occur at installation and are defined by entries in the JAD file. Dynamic registrations occur at runtime by calls to the registerConnection() method. The push registry also provides methods to retrieve information about the push connections associated with the MIDlet suite. PushRegistry's methods throw security and other exceptions that you should catch to make your MIDlet robust.

The responsibility for push is shared between the MIDlet and the push registry in the AMS. The MIDlet is responsible for registering and unregistering inbound connections and timer alarms. When it's active, it's responsible for setting up and waiting for connections, and for the scheduling of any cyclic tasks, typically by using a TimerTask. When the MIDlet is not running, the AMS will do the timer and inbound connection monitoring and will activate the appropriate MIDlet when it detects push activity is detected.

To support push, the platform must support inbound connection types, typically socket, datagram, or WMA. For a MIDlet to be able to receive a push event, it must have an address that allowed senders know. For SMS messages, the push address is the phone's number (and port). For socket and datagram connection types, it's an IP address. If the IP address is static (well known) then it's easy for anyone to push to the device, but if the IP address is dynamic, the MIDlet is responsible for publishing this address so external agents can push information to it.

You must request permission to use the push registry and inbound network connections using the new MIDP 2.0 permission framework.

You can use version 2.0 of the J2ME Wireless Toolkit to test your push MIDlets.

Acknowledgments

Thanks to Gary Adams for his contributions to this article.

Companion Code

Any use of this code and/or information below is subject to the license terms: License.

The companion code for this article follows. BasicPushMIDlet.java contains all the code you've seen in this article. With this MIDlet you can register an alarm (schedule the MIDlet), register and unregister an inbound connection, and list all the registered connections for the MIDlet suite.

fig 7

Figure 7. The BasicPushMIDlet UI

Also provided are the JAD, JAR manifest, and build.xml files.

/*
 *  BasicPushMIDlet.java  Janyary 2002
 *  Author: Sun Microsystems, Inc.
 *
 *  BasicPushMIDlet.java is the companion source
 *  code to the "MIDP 2.0
 *  PushRegistry" article
 *
 *  This sample MIDlet demostrates how to use MID 2.0 PushRegistry.
 */
package j2medeveloper.basicpush;

import java.util.*;
import java.io.*;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;

/**
 *  PushMIDlet - a Push sample MIDlet
 */
public class BasicPushMIDlet
    extends MIDlet implements CommandListener,
        Runnable {

//  Default values to use.
String midletClassName  = this.getClass().getName();
String defaultURL = "socket://:6000";
String defaultFilter = "*";
long defaultDeltaTime = 15000;
long REFRESH_TIME = 1000*60*5; // every 5 minutes

//  User Interface related
private Command scheduleCmd  = new Command(
    "Schedule",Command.SCREEN, 1 );
private Command regCmd       = new Command(
    "Register",Command.SCREEN, 2 );
private Command unregCmd     = new Command(
    "UnRegister",Command.SCREEN, 3 );
private Command listCmd      = new Command(
    "List",Command.SCREEN, 4 );
private Command thCmd;
protected Display display;
private Ticker ticker;
private Form form;


/** Constructor */
public BasicPushMIDlet() {
}

/**
 *  Initial state.
 *  @throw MIDletStateChangeException to indicate
 *  a transient error has occured.
 */
public void startApp() throws MIDletStateChangeException {
    try {
        if (display == null) {
            System.out.println("Setting up...");
            display = Display.getDisplay(this);
            //  Create a Ticker and a Form, add commands
            //  to the form to test PushRegistry alarms and
            //  inbound connections, set the Form'scommand
            //  listener, and the Ticker to the Form.
            ticker = new Ticker("");
            form = new Form("Basic Push Test");
            form.addCommand(scheduleCmd);
            form.addCommand(regCmd);
            form.addCommand(unregCmd);
            form.addCommand(listCmd);
            form.setCommandListener(this);
            form.setTicker(ticker);
            display.setCurrent(form);

            //  Discover if activation is due to an inbound
            //  connection.
            if (isPushActivated() == true) {
                ticker.setString("Push Activated");
            }
        }
    }
    catch(Exception e) {
        System.out.println("Exception during
            startApp()");
        e.printStackTrace();
        //  If some kind of transient error ocurrs, throw a
        //  MIDledStateChangeException.
        throw new MIDletStateChangeException("Error
            Starting...");
    }
}

/** Paused state. Release resources (connection, threads, etc). */
public void pauseApp() {
    display = null;
}

/**
 *  Destroyed state. Release resources (connection,
 *  threads, etc). Also, schedule the future launch
 *  of the MIDlet. @param uc If true when this method
 *  is called, the MIDlet must cleanup and release all
 *  resources. If false the MIDlet may throw @throw
 *  MIDletStateChangeException  to indicate it does not
 *  want to be destroyed at this time.
 */
public void destroyApp(boolean uc) throws
    MIDletStateChangeException {
    display = null;
    try {
        //  Set up the alarm and force the MIDlet to exit.
        scheduleMIDlet(defaultDeltaTime);
    }
    catch(ClassNotFoundException e) {
        System.out.println("Exception during
            destroyApp()");
        e.printStackTrace();
    }
    catch(ConnectionNotFoundException e) {
        System.out.println("Exception during
            destroyApp()");
        e.printStackTrace();
    }
}

/**
 *  Command/button listener.
 *  @param c the LCDUI Command to process.
 *  @param d the Displayable source of the Command.
 */
public void commandAction(Command c, Displayable d) {
    thCmd = c;
    //  Dispatch a thread to process commands.
    if (c == scheduleCmd || c == regCmd || c ==
        unregCmd || c == listCmd) {
            Thread th = new Thread(this);
            th.start();
    }
} // commandAction()

/** Thread run method. */
public void run() {
    try {
        //  Do alarm or inbound connection test based
        //  on value of last Command
        if (thCmd == scheduleCmd) {
            // cyclic background task info.
            Timer aTimer = new Timer();
            MyTask myTask = new MyTask();
            aTimer.schedule(myTask, 0, defaultDeltaTime);
        }
        else
        if (thCmd == regCmd) {
            //  Register connection
            PushRegistry.registerConnection(defaultURL,
                midletClassName, defaultFilter);
        }
        else
        if (thCmd == unregCmd) {
        //  Unregister connectioun
        boolean status =
            PushRegistry.unregisterConnection(defaultURL);
        }
        else
        if (thCmd == listCmd) {
            //  Dump push info
            outputPushInfo();
        }
    }
    catch(Exception e) {
        e.printStackTrace();
    }
}

/**
 *  Determine if activated due to inbound connection.
 *  @return true if MIDlet was activated due to
 *  inbound connection, false otherwise
 */
private boolean isPushActivated() {
    //  Discover if there are pending push inbound
    //  connections and if so, dispatch a PushProcessor
    //  for each one.
    String[] connections =
        PushRegistry.listConnections(true);
    if (connections != null &&
        connections.length > 0) {
        return(true);
    }
    return(false);
}

/**
 *  Determine if activated due to inbound connection
 *  and if so dispatch a PushProcessor to handle incoming
 *  connection(s). @return true if MIDlet was activated
 *  due to inbound connection, false otherwise
 */
private boolean handlePushActivation() {
    //  Discover if there are pending push
    //  inbound connections and if so, dispatch
    //  a PushProcessor for each one.
    String[] connections =
        PushRegistry.listConnections(true);
    if (connections != null && connections.length > 0) {
        System.out.println("is PushActivated");
        for (int i=0; i < connections.length; i++) {
            PushProcessor pp = new
                PushProcessor(connections[i]);
        }
        return(true);
    }
    return(false);
}

/**
 * Output push info for all push connections for the
 * MIDlet suite
 */
private void outputPushInfo() {
    //  For all registered inbound connections output
    //  the push info for each one.
    String[] connections =
        PushRegistry.listConnections(false);
    if (connections != null && connections.length > 0) {
        for (int i=0; i < connections.length; i++) {
            String midlet =
                PushRegistry.getMIDlet(connections[i]);
            String filter =
                PushRegistry.getFilter(connections[i]);
            // Output the info.
            System.out.println("PushInfo -> " +
                connections[i] + " " + midlet +
                    " " + filter);
            form.append(connections[i] + " " +
                midlet + " " + filter);
        }
    }
}

/**
 *  Set up a PUSH alarm event.
 *  @param et is the expiration delta time in milliseconds.
 */
private void scheduleMIDlet(long et)
    throws ClassNotFoundException,
        ConnectionNotFoundException,
        SecurityException {
    Date alarm = new Date();
    String midletClassName  = this.getClass().getName();
    long t = PushRegistry.registerAlarm(
        midletClassName, alarm.getTime()+et);
}

/**
 *  Example on registering a dynamic socket connection.
 */
private void registerDynSocket() {

    //  MIDlet class name.
    String midletClassName  = this.getClass().getName();
    //  Register a dynamic connection.
    String url = "socket://";
    //  Use an unrestricted filter.
    String filter = "*";

    try {
        // Open the connection.
        ServerSocketConnection ssc =
            (ServerSocketConnection) Connector.open(url);
        // Discover the system-assigned port.
        url = "socket://:" + ssc.getLocalPort();
        // Register the connection now. The AMS will
        // remember the registered URL even when the MIDlet
        // is not active.
        PushRegistry.registerConnection(url,
            midletClassName, filter);
        // Now publish the push URL. We can use an HTTP
        // POST or a socket or datagram for this.
        String purl;
        purl = "socket://"+ssc.getLocalAddress()+
            ":"+ssc.getLocalPort();
        publishInboundConnection(purl, midletClassName);
    }
    catch(SecurityException e) {
        e.printStackTrace();
    }
    catch(IOException e) {
        e.printStackTrace();
    }
    catch(ClassNotFoundException e) {
        e.printStackTrace();
    }
}

/**
 *  Example on registering a dynamic datagram connection.
 */
private void registerDynDatagram() {

    //  MIDlet class name.
    String midletClassName  = this.getClass().getName();
    //  Register a dynamic connection.
    String url = "datagram://";
    //  Use an unrestricted filter.
    String filter = "*";

    try {
        // Open the connection.
        UDPDatagramConnection udgc = (UDPDatagramConnection)
            Connector.open(url);
        // Discover the system-assigned port.
        url = "datagram://:" + udgc.getLocalPort();
        // Register the connection now. The AMS will remember
        // the registered URL even when the MIDlet is not
        // active.
        PushRegistry.registerConnection(url, midletClassName, filter);
        // Now publish the push URL. We can use an HTTP
        // POST or a socket or datagram for this.
        String purl;
        purl = "datagram://"+udgc.getLocalAddress()+
            ":"+udgc.getLocalPort();
        publishInboundConnection(purl, midletClassName);
    }
    catch(SecurityException e) {
        e.printStackTrace();
    }
    catch(IOException e) {
        e.printStackTrace();
    }
    catch(ClassNotFoundException e) {
    e.printStackTrace();
    }
}

/**
 *  An example of a method to publish dynamic inbound
 *  address.
 *  @param url is the inbound address
 *  @param midlet is the name of the midlet
 */
private void publishInboundConnection(String url, String midlet) {
    String hostToPostTo = "j2medeveloper:7000/pushagent";
    OutputStream os = null;
    HttpConnection hc = null;
    try {
        if (hostToPostTo.startsWith("http://") == false) {
        hostToPostTo = "http://" + hostToPostTo;
        }
        hc = (HttpConnection)Connector.open(hostToPostTo);
        //  Set HTTP request method to POST,set Content-Type
        //  and send body.
        int len = url.length() + midlet.length();
        String pushinfo = "pushurl=" + url +
            ", pushmidlet=" + midlet ;
        hc.setRequestMethod(HttpConnection.POST);
        hc.setRequestProperty("User-Agent",
            "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
        hc.setRequestProperty("Content-Language", 
            "en-US");
        hc.setRequestProperty("Accept",
            "text/xml");
        hc.setRequestProperty("Connection",
            "close");

        hc.setRequestProperty("Content-Type",
            "application/x-www-form-urlencoded");
        hc.setRequestProperty ("Content-Length",
            ""+pushinfo.length());
        os = hc.openOutputStream();
        os.write(pushinfo.getBytes());
    }
    catch(Exception e) {
        e.printStackTrace();
    }
    finally {
        try {
            if (os != null)
                os.close();
            if (hc != null)
                hc.close();
        }
        catch (IOException e) {
        e.printStackTrace();
        }
    }
}

//-----------------------------------------------------

/**
 *  MyTask - Thread responsible of cyclic processing
 *  while the MIDlet is active. Add your cyclic logic
 *  to the run() method.
 */
class MyTask extends TimerTask {
    // Constructor.
    public MyTask() {
    }
    // .....
    // Thread run method.
    public void run() {
        // ..... Your Task Logic
    System.out.println("Cycling...");
    }
}

//-----------------------------------------------------

/**
 *  PushProcessor - Thread of execution responsible of
 *  receiving and processing push events.
 */
class PushProcessor implements Runnable {

    Thread th = new Thread(this);
    String url;
    boolean done = false;
    String midletClassName;

    /** Constructor */
    public PushProcessor(String url) {
        this.url = url;
        th.start();
    }

    public void notifyDone() {
    done = true;
}

/**
 *  Thread's run method to wait for and process
 *  received messages.
 */
public void run() {
    eServerSocketConnection ssc = null;
    SocketConnection sc = null;
    UDPDatagramConnection udgc = null;
    Datagram dg = null;
    InputStream is = null;
    try {
        while(!done) {

            if (url.startsWith("socket://")) {
                //  "Open" connection.
                ssc = (ServerSocketConnection)
                Connector.open(url);
                //  Wait for (and accept) inbound connection.
                sc = (SocketConnection)
                ssc.acceptAndOpen();
                is = sc.openInputStream();
                //  Read data from inbound connection.
                int ch;
                byte[] data = null;
                ByteArrayOutputStream tmp = new
                    ByteArrayOutputStream();
                while( ( ch = is.read() ) != -1 ) {
                    tmp.write( ch );
                }
                data = tmp.toByteArray();
                //--------------------------------------+
                //  Here do something with received data|
                //--------------------------------------+
                System.out.print(new String(data));
            }
            else {
                if (url.startsWith("datagram://")) {
                    // Open the connection.
                    udgc = (UDPDatagramConnection)
                    Connector.open(url);
                    // Now wait for inbound network
                    // activity.
                    dg = udgc.newDatagram(udgc.getNominalLength());
                    // Read the inbound messages
                    while (!done) {
                        udgc.receive(dg);
                        //-------------------------+
                        //  Here do something with |
                        //  received data          |
                        //-------------------------+
                        System.out.print(new
                        String(dg.getData()));
                    }
                }
            }
        }
    }
    catch (IOException e) {
    System.out.println("PushProcessor.run
    Exception" + e);
    e.printStackTrace();
    }
    finally {
        try {
            if (is != null) is.close();
            if (sc != null) sc.close();
            if (ssc != null) ssc.close();
            if (udgc != null) udgc.close();
        }
        catch(Exception e) {
        }
    }
}

} //  PushProcessor

} //  PushMIDlet

Listing 15. The Companion BasicPushMIDlet.java

MIDlet-1: PushMIDlet,,j2medeveloper.basicpush.
    BasicPushMIDlet
MIDlet-Description: PushRegistry samples
MIDlet-Name: Basic Push
MIDlet-Vendor: Sun Microsystems, Inc.
MIDlet-Version: 1.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0
MIDlet-Jar-Size: 6587
MIDlet-Jar-URL: basicpush.jar
MIDlet-Push-1: socket://:5000,
    j2medeveloper.basicpush.BasicPushMIDlet, *
MIDlet-Permissions: javax.microedition.io.PushRegistry,
    javax.microedition.io.Connector.serversocket

Listing 16. The Companion JAD file

MIDlet-1: PushMIDlet,,j2medeveloper.basicpush.
BasicPushMIDlet
MIDlet-Description: PushRegistry samples
MIDlet-Name: Basic Push
MIDlet-Vendor: Sun Microsystems, Inc.
MIDlet-Version: 1.0
MicroEdition-Configuration: CLDC-1.0
MicroEdition-Profile: MIDP-2.0

Listing 17. The Companion Manifest File




Reader Feedback
Excellent   Good   Fair   Poor  

If you have other comments or ideas for future technical tips, please type them here:

Comments:
If you would like a reply to your comment, please submit your email address:
Note: We may not respond to all submitted comments.


Back To Top