Interapplication Communication with Qualcomm Brew

Interapplication Communication with Qualcomm Brew

wo heads are better than one, goes the old saying, and that can be as true for mobile applications as it is the people who write them. Whether you’re looking to partition functionality between related applications, or integrate two applications to work together to share data, if you’re working in the mobile space, you should understand Brew’s interapplication paradigm. This article willshow you how to have applications communicate via launch arguments, events, URLs, and Brew’s IFIFO, a kernel-level pipe interface you can establish between applications.

Brew Interprocess Communications Overview
As you already know, Brew provides a single-process execution environment with an application stack. The frontmost application?that’s the one you’re interacting with?is at the top of the stack, and other applications that have been previously launched and are suspended are at lower levels of the stack. An application may also run in the background, in which case its context remains loaded and it can receive events from the system or other applications, but shouldn’t draw to the screen.

All of these applications can communicate in four different ways:

  • Applications may pass events to each other.
  • One application may start another and include launch arguments for the started application to process as it begins execution.
  • Applications can pass URLs anonymously to other applications (this is a specialized form of launch argument handling).
  • An application can establish a fifo to which other applications can write.

Events in Brew
The Brew Application Execution Environment (AEE) includes an event pump that accepts events from the system and other sources and directs them to applications. Using the twin methods ISHELL_SendEvent and ISHELL_PostEvent, your application can direct an event to any other application resident on the device. With the event?a thirty-two bit integer?you can send two arguments: a sixteen bit integer and a thirty-two bit integer, collectively called the event arguments. The system reserves a range of events for system notifications such as incoming keystrokes, alarms, and SMS messages; any event numbered greater than the Brew-defined constant EVT_USER is fair game.

When sending an event to another application, it’s important to know the difference between ISHELL_SendEvent and ISHELL_PostEvent. The former is synchronous, that is, the recipient’s event handler is invoked before ISHELL_SendEvent returns. ISHELL_PostEvent, on the other hand, is asynchronous; your event is queued and will be sent to the recipient application after ISHELL_PostEvent returns. Which you choose to use is largely up to you, but you should be aware of two things:

  • Because ISHELL_SendEvent is synchronous, the amount of stack space available to the recipient may be significantly less than if you use ISHELL_PostEvent.
  • Anything you’re signalling about using ISHELL_PostEvent must persist beyond the lifetime of the calling function, because it’s asynchronous and the receiver won’t receive the event until your application has yielded control to the Brew run time.

An obvious mode of interapplication communication, then, is to agree on event code and argument syntax, and just have two applications pass events around. If you and I agree that EVT_USER+1 means something?say, for my application to show a particular screen?your application can invoke mine by sending me that event, and I can handle it by launching using ISHELL_StartApplet and going to that screen.

This is unnecessarily brittle, however. Brew provides the ISHELL_RegisterEvent call, which takes a character string and returns a unique event associated with that character string. If more than one application invokes this API with the same character string, the same event is returned. Thus, instead of agreeing on a specific constant (EVT_USER+1) it’s better for us to agree on a well-defined character string, such as com.rocketmobile.myapp.launchtolistview, and let Brew handle the event code mapping under the hood. On boot, my application can register this event, and your application can register the same event when it needs to invoke mine, and Brew will handle the string-to-event code mechanism.

A word about the event arguments is in order. Because one of the event arguments is a thirty-two-bit integer, it’s very tempting to overload this integer and use it as a pointer, as Brew does with many events such as EVT_APP_MESSAGE. As I write this, this actually works?an application can use ISHELL_SendEvent to send an event and a pointer to its private data to another application, and another application can accept the integer, cast it to a pointer of the appropriate type, and dereference the pointer in the first application’s heap. This is a particularly nasty solution to passing large quantities of data between two applications for at least three reasons:

  • There is no type safety in this transaction. If the sending or receiving application mis-casts the pointer, the data will not be interpreted correctly and one or more applications may crash as a result.
  • Qualcomm has indicated that in future versions of Brew, each application will run in its own memory space, so that applications cannot share pointers to the same data.
  • There are bettter ways to do it, which is what the rest of this article is about.

For these reasons, I strongly recommend using one of the following means to pass anything more than a sixteen or thirty-two bit integer between two applications.

Starting an Application with Arguments
Most of the time, starting an applet is as easy as calling ISHELL_StartApplet with the class ID of the application to start. But there’s also ISHELL_StartAppletArgs, which takes an additional character string. When Brew handles this API, it makes a copy of the character string you provide, and sends it to the application being started as the pszArgs element of the AEEAppStart structure pointed to by the dwParam argument of the EVT_APP_START or EVT_APP_RESUME event. Thus, if your application invokes the following:

ISHELL_StartAppletArgs( pMe->a.,m_pIShell,                         AEECLSID_MYAPP, "listview" );

and my application’s class ID is AEECLSID_MYAPP is found on the device, it will be created and sent an EVT_APP_START (or, if it’s already running, it will receive EVT_APP_RESUME), and I can get the arguments you pass me with code such as the following in my event handler: EVT_APP_START:case EVT_APP_RESUME:{	AEEAppStart *pArgs = (AEEAppStart *)dwParam;	if ( pArgs && pArgs->pszArgs &&         0==STRCMP(pArgs->pszArgs, "listview" )		ShowListView( pMe );	else		ShowNormalView( pMe );	bHandled = TRUE;}break;...return bHandled;

The beauty of ISHELL_StartAppletArgs is that anything can be passed, so long as it’s a reasonable-length string. It’s an excellent means of chaining one application to another, so that one application can offer a particular feature such as media download, while another application offers a related feature, such as content search. It does have its drawbacks, however.

First, the data you pass must be a null-terminated string of reasonable length, because the Brew runtime is going to use some equivalent of STRDUP() to make a copy of the arguments you provide before passing it to the receiving application. If you’re looking to pass a large blob of binary data, a FIFO is a better choice, as shown in a subsequent section of this article.

Second, there’s no format imposed on the character string you pass. This is both good and bad. It gives you the freedom to use whatever you want, but at the same time, it’s up to you to create a parser to interpret the string you’re receiving. There’s no equivalent to the UNIX getopt for Brew; over the years I’ve seen some pretty esoteric means of getting arguments from one application to another as a result.

Third, you have to know the class ID of the receiving application. It’s not enough to know what to say, but you have to know to whom your message is being sent. While this usually isn’t a problem?interapplication communication is usually used by closely coupled parties such as two development teams at one firm or two firms working together?it imposes unnecessary coupling between the two applications.

To address these concerns?the format of the arguments and the coupling of between applications?you should use ISHELL_BrowseURL or ISHELL_PostURL instead.

Passing an Application to a URL
Any Brew application can request another application to handle URLs using the ISHELL_BrowseURL or ISHELL_PostURL functions through the Brew AEE’s URL and MIME type handling registries. Using this mechanism:

  • Applications that can handle specific Web protocols register their ability in their Module Information File.
  • A client application requesting a service creates a URL for the specific Web protocol, embedding the arguments to the request in the URL using the standard mechanism used by Web servers handling GET requests.
  • The client application invokes ISHELL_BrowseURL to launch the application associated with the desired protocol, or ISHELL_PostURL to request a service without launching the application that handles the desired protocol.
  • The Brew AEE searches its registry of Web protocol and MIME handlers for a class ID that can handle the protocol in the URL you pass.
  • The Brew AEE sends the URL to the handling application via either the EVT_BROWSE_URL (if you used ISHELL_BrowseURL) or the EVT_APP_POST_URL (if you used ISHELL_PostURL).

To use one of these APIs to launch the hypothetical application to the list view, you might write:

ISHELL_LaunchURL( pMe->a.m_pIShell,                   "rayapp://listview" );

The application here would need to add the rayapp protocol to the list of handled protocols in the module information file, and then use IWEBUtil to crack the incoming URL in the event handler, like this:

case EVT_BROWSE_URL:{	char *pszUrl = (char *)dwParam;	UrlParts sParts = {};	IWEBUTIL_ParseUrl( pMe->piwu, pszUrl, &sParts );		if ( sParts.cpcPath &&         0 == STRCMP( sParts.cpcPath, "listview" ) )		ShowListView( pMe );	else		ShowNormalView( pMe );	bHandled = TRUE;}break;

Of course, the URL you pass to me might have arguments, too; you could pass something like rayapp://listview?style=fancy;items=one,two,three, and I can parse the URL arguments starting from the UrlPart’s cpcSrch pointer.

As I hinted previously, ISHELL_LaunchURL launches the registered application to the foreground; if what you want is background processing of the content, use ISHELL_PostURL instead. From there, your application can process the URL and even start itself in the foreground or background as appropriate.

URLs are excellent containers for small parcels of data like launch parameters, but sometimes you need something bigger. For this, there’s IFIFO.

Since Brew 1.x, applications have been able to share data via the file system by putting files in the shared directory. Later versions of Brew improve upon this convention by providing access control lists (ACLs), permitting modules to share access to their private directories. While this has been an acceptable means to share large quantities of data between applications, it can have stiff performance penalties, as applications must read and write the data being shared to the flash file system.

In Brew 3.x, Qualcomm introduced the IFIFO interface, which as its name suggests, is a first-in-first-out queue for interapplication communication. Applications using IFIFO specify the location of the fifo as they open it using a file path preceded by the string fifo:/, i.e., fifo:/shared/rayapp or fifo:/~0x123456/rayapp. The first example denotes a fifo in the shared file system that can be accesed by any application; the latter a fifo in a specific module’s directory that can only be accessed by applications that have the appropriate entry in their ACL.

Once a FIFO is opened, it can can be read from or written to depending on the access mode they specify when opening the fifo. For example, to open the fifo located at /shared/rayapp for reading and writing, I would refer to the fifo as fifo:/shared/rayapp?mode=rw, like this:

result = ISHELL_CreateInstance( pMe->a.m_pIShell, AEECLSID_FIFO, (void **)&pMe->pififo );if ( result == SUCCESS ){	result = IFIFO_Open( pMe-

Once open, the using the fifo is just like using any other Brew socket. You must see if you can write to the fifo by calling IFIFO_Writable with a callback, and in your callback, invoke IFIFO_Write. Similarly, to read data, set a callback using IFIFO_Readable and read the data using IFIFO_Read, looking for ISOURCE_WAIT, ISOURCE_ERROR, and ISOURCE_END values, indicating that all of the data has been read, an error has occured, or the writer has closed the fifo, respectively.

You can also set and query the fifo's buffer size (within reason) using IFIFO_SetBufSize and IFIFO_GetBufSize. Equally useful is IFIFO_GetBufUsed, which returns the number of bytes used in the buffer.

When using a fifo, two questions come to mind: in what format should the data be, and how should listeners wait for the data? The answer to the former is largely application-dependent, but of course you should consider platform-agnostic types such as media types, XML, and YAML, to make the most of decoupling between your applications.

Figure 1. Picture This: Drawing lines using different Pens.

The second question also depends on the nature of your application, but in practice an approach coupling an event with a specific fifo can work well. When a client application needs to request a service of the serving application, it registers an event and invokes ISHELL_SendEvent to the serving application. The serving application registers the same event on module load, then responds to the event by opening the established fifo for reading and launches to the background. The client application is then free to write its request to the fifo, and the serving application can respond via events or a second fifo as appropriate (see Figure 1).

Brew's Come a Long Way, Baby
Interapplication communication has come a long way since the lowly shared file and mutually agreed upon event found in the first versions of Brew. Using either URLs for simple data types or fifos for complex data blocks, it's now possible to create sophisticated client-server relationships between applications in Brew.


Share the Post: