April 17, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Three Tricks for Faster Handset Ports with Qualcomm BREW

  • June 19, 2006
  • By Ray Rischpater
  • Send Email »
  • More Articles »

In the world of server and desktop software development, the product development cycle is often measured in months or years. Not so in mobile development, where carrier certification requirements force you to release specific builds of your application for each target on which it will run. Even if you're planning on a long development cycle between features, to remain competitive you must allocate resources to provide ports of your application on as many handsets as possible. Sometimes, this isn't just common sense but a requirement of carriers, who want to see as many applications as possible available on the broadest range of handsets as possible.

Spending a bit of time up-front to consider the effort required to keep your application running on the latest hardware can go a long way to streamline the application porting process once your application is on the market. Developers have praised the Qualcomm BREW platform for ease of porting-in most cases ports between handsets can take only a few days-but with planning you can make this success story more likely to happen for your product.

Key to speedy handset ports is reducing the changes you must make to your application binaries between releases: it's obvious that the less code you change, the less likely you are to introduce other problems within your application. The three tricks I outline here all have that goal in mind, letting one application run seamlessly across many different BREW-enabled handsets.

Know Your Device Capabilities

First and foremost, it's crucial you know the capabilities of your target handsets, both now and in the future. Surveying the features-screen size, media support, heap resources, and so forth-of handsets on the market today is crucial when designing your application; keeping current and learning what a specific target supports is required before trying to bring up your application on new hardware. The Qualcomm Web site provides data sheets for both current and preproduction hardware; these data sheets are required reading at my office for anyone involved in handset porting.

Becoming familiar with device capabilities helps you create a lowest-common-denominator approach to many aspects of your application, such as user interface and media formats. For example, a glance at the data sheets immediately suggests that while there are a wide variety of screen sizes, today's BREW-enabled handsets typically sport screens of one of the following types:

  • Small. Small devices, such as the Audiovox CDM-180, have screens small enough to be the secondary displays on larger handsets. Often barely over a hundred pixels on a side, screens on these devices may odd aspect ratios as well. Not quite in this category, but not as large as medium screens, are the first color handsets that often had screens that were around 128x176 pixels or so.
  • Medium. Many devices now use a standard size of 176x220 pixels, regardless of manufacturer or carrier. Moreover, a number of handsets use screens roughly that size, usually a few pixels within that size along one or both dimensions.
  • Large. Many high-end handsets are moving towards providing a QVGA screen in either portrait or landscape layout.

Binning handset screens into these categories-small, medium, and large-makes designing user interfaces much easier. A good GUI designer can work to provide one set of screen layouts for an application that works well for all three screen sizes, and then build appropriate images and text for each of those three profiles. Application developers should use dynamic layout whenever possible when building the code that lays out each screen (using BREW uiOne, constraint and proportional containers, or brute-force arithmetic with older or home-grown controls) to ensure the same look and feel across all handsets.

This approach is effective whenever you're setting out to design your application. Other areas you should consider binning and looking for commonalities include media support (supported image types or audio types) and supported interfaces. For back-of-the-envelope estimation purposes, it's safe to assume that a specific series of handsets from a single manufacturer running a specific version of BREW probably (but not always) will support the same media types. But before you plan your product launch, it's wise to check the data sheets!

Discover Handset Features at Run Time

If different handsets require different code paths-say your application uses BCI images on handsets made by LG and PNG images on handsets made by Samsung - you have two choices. You can either make the decision at compile time and create two different binaries, or you can make the decision at run time. To do this, however, you need to know what kind of handset is executing your application. You can do this using the ISHELL_DeviceInfo function, consulting your handy device sheets for the platform id's for the handsets you're supporting:

AEEDeviceInfo di = {0};

di.wStructSize = sizeof( AEEDeviceInfo );
ISHELL_GetDeviceInfo( pMe->a.m_pIShell, &di );
switch( di.dwPlatformID )
{
  case xxx:
    pMe->dwImageClsID = AEECLSID_BCI;
    break;
  case yyy:
    pMe->dwImageClsID = AEECLSID_PNG;
    break;
}

This works, and for the handsets you're supporting now, lets you use a single binary. But what about when LG or Samsung ships a new handset? You will need to go back and extend the terms of your case statement again to include the new class id's. Granted that this isn't a terribly risky change, anything that opens up the binary to change injects risk.

There's a better way. If you're interested in providing these abstractions at run time, you can simply embed them in a configuration file you read at run-time. Reading and parsing a small name-value list at runtime is a fast operation; even parsing a configuration file in XML takes no noticeable time to the user. Once this is done, you can create a configuration file for each of LG and Samsung handsets; the LG handset might read:

Image_classid: 16793606

And then you can read the configuration file using the IFileMgr, IFile, and IGetLine interfaces. Using IGetLine, you can obtain a single line using IGETLINE_GetLine, break it into its name and value parts, and then extract the information you need. In fact, if you're running on BREW 2.x and greater handsets, you can just load the data into an IRamCache and query the IRamCache when you need the data:

#define NEW_INTERFACE( cls, p )
  ISHELL_CreateInstance( pMe->a.m_pIShell, cls, (void **)&p )

#define ANOTHER_INTERFACE( previous_result, cls, p ) 
( ( result ) == SUCCESS ) ? 
  ISHELL_CreateInstance( pMe->a.m_pIShell, cls, (void **)&p ) : ( result );

#define RELEASEIF( p ) if (p) { IBASE_Release((IBase *)p); p = 0; }
#define MAX_LINE_LEN 128

int App_LoadConfig( CApp *pMe )
{
  GetLine sLine = {0};
  IFileMgr *pifm = NULL;
  IFile *pif = NULL;
  ISourceUtil *pisu = NULL;
  IGetLine *pigl = NULL;
  ISource *pis = NULL;
  int result = EFAILED;
  int r;

  result = NEW_INTERFACE( AEECLSID_FILEMGR, pifm );
  result = ANOTHER_INTERFACE( result, AEECLSID_SOURCEUTIL, pisu );
  if ( SUCCESS == result )
  {
    pif = IFILEMGR_OpenFile( pifm, "config.txt", _OFM_READ );
    if ( pif )
    {
      result = ISOURCEUTIL_SourceFromAStream( pisu,
                                              (IAStream *)pif, &pis );
      if ( SUCCESS == result )
        ISOURCEUTIL_GetLineFromSource( pisu, pis, MAX_LINE_LEN, &pigl );
      if ( SUCCESS == result )
      {
        // While there are lines in the file, get a
        // line, fracture it, and stash the values in the cache.
        do
        {
          r = IGETLINE_GetLine( pigl, &sLine, IGETLINE_CR );
          if ( sLine.nLen > 0  )
          {
            char *pSep = STRCHR( sLine.psz, '=' );
            IRAMCACHE_Add( pMe->pIConfigCache, 
                           sLine.psz, pSep - sLine.psz, 
                           pSep+1, STRLEN( pSep ) );
          }
        } while ( IGETLINE_END !=r &&
                  IGETLINE_ERROR != r);
      }
    }
    else
    {
      result = IFILEMGR_GetLastError( pifm );
    }
  }

  RELEASEIF( pifm );
  RELEASEIF( pif );
  RELEASEIF( pisu );
  RELEASEIF( pis );
  RELEASEIF( pigl );
  return result;
}

This is all pretty basic stuff: create the necessary IFileMgr and ISourceUtil interfaces, open the file, and get an GetLine interface for the file using ISourceUtil. With that, the code reads one line at a time, separating the name and value pairs using the STRCHR call, and inserts each name/value pair into the cache.

This trick lets you abstract not just simple things like class id's of needed interfaces that way, but whole bits of program behavior. Instead of using conditional-compile flags, you can embed the appropriate code for different handsets (say, a work-around to a platform issue) in a function, and test against the value of the flag in the configuration file before invoking the appropriate function.



Page 1 of 2



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel