The State and Notifications Broker Part I

 

Jim Wilson
JW Hedgehog, Inc.

February 2006

Applies to:
   Microsoft .NET Compact Framework version 2.0
   Microsoft Visual Studio 2005
   Windows Mobile 5.0

Summary: In this month's column, Jim introduces the State and Notifications Broker and its role in enabling applications to work more closely with the Windows Mobile platform. The discussion covers the underlying architecture of the State and Notifications Broker, the use of the State and Notifications Broker API to retrieve more than 100 different state values, and how an application can cooperate with the State and Notifications Broker to receive notifications about changes to important state values, such as a change in network availability. This column covers both native and managed code usage of the State and Notifications Broker API.

This month's column is part one of a two-part examination of the State and Notifications Broker and is the first in a series of many deep-dive discussions about the new features Windows Mobile 5.0, the .NET Compact Framework 2.0 and Visual Studio 2005 provide. (20 printed pages)

Contents

Introduction
Getting Started with the State and Notifications Broker
Retrieving Windows Mobile State Information
Receiving Notifications
Conclusion

Introduction

As I mentioned in my previous column, A View of Windows Mobile 5.0 from 10,000 Feet, we are going to spend the next several months taking a detailed look at the new features that Windows Mobile 5.0, Visual Studio 2005, and the .NET Compact Framework version 2.0 offer developers. This first article in the series provides a detailed look at the Windows Mobile 5.0 State and Notifications Broker API.

I have to say that choosing the first feature to investigate was actually easier than I thought. I'm not saying that Windows Mobile 5.0, Visual Studio 2005, and the .NET Compact Framework 2.0 don't offer a lot of great new features—they certainly do. But the State and Notifications Broker API is one of those APIs that literally opens up a whole world of opportunities. Common situations that have historically been difficult to deal with programmatically become quite easy to manage—such as detecting changes in network connectivity, locating the Pocket Outlook contact that corresponds to the incoming caller, or determining that the device battery has become critically low. Similarly, situations where you have an application that needs to publish changes in the application's state or share data with other applications become significantly easier with the State and Notifications Broker API.

The State and Notifications Broker API offers too many features to cover in a single edition of this column, so I'm going to divide this discussion about the State and Notifications Broker API into two parts. In part I, we'll look at using the State and Notifications Broker API to more closely integrate your applications with the device and built-in Windows Mobile applications. In part II, we'll discuss using the State and Notifications Broker API in some more advanced situations, and we'll also look at how to expose state information from your own applications.

Getting Started with the State and Notifications Broker

As the name implies, the State and Notifications Broker provides two services. First, it acts as a central repository for state information without regard for the source of that information. Second, it provides a standard architecture for monitoring those state values for changes and distributing change notifications to the list of interested parties. Fundamentally the State and Notifications Broker is a data store that provides a standardized publish-subscribe model for distributing data change notifications. Don't let this simple definition fool you. The State and Notifications Broker is a very powerful tool.

With the State and Notifications Broker, Windows Mobile 5.0 changes a smart device from a device that simply runs a bunch of independent applications to a holistic platform where applications can easily share information and respond to changes in the device's state and to the state of other applications. The State and Notifications Broker provides the user with a high degree of continuity between individual applications and creates a device that, in a sense, runs a single meta-application made up of the best features of the individual applications that run on the device.

Remember that the State and Notifications Broker and the corresponding API, the State and Notifications Broker API, are part of the Windows Mobile 5.0 platform and, therefore, require you to install Visual Studio 2005 and the Windows Mobile 5.0 SDK for Pocket PC or Smartphone on your desktop computer.

Note See the Windows Mobile 5.0 Developer Tools section in last month's column for the complete list of downloads and installations that are required to develop Windows Mobile 5.0 applications.

Locating the State and Notifications Broker API SDK Files

The exact location of the Windows Mobile 5.0 SDK on your desktop computer depends on which Windows Mobile 5.0 SDK you've installed. If you install the Windows Mobile 5.0 SDK for Smartphone and accept the default installation path, the SDK files are installed in \Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Smartphone SDK\. If you install the Windows Mobile 5.0 SDK for Pocket PC, you need to substitute Pocket PC for Smartphone in the previous path. With the Windows Mobile 5.0 SDK installed on their desktop computers, both managed-code and native-code developers have everything they need to use the State and Notifications Broker API.

The classes that are contained in the Microsoft.WindowsMobile.Status assembly expose the State and Notifications Broker API to managed-code developers. With the Windows Mobile 5.0 SDK installed, you can add a reference to the assembly by simply selecting it from the Add Reference dialog box in Visual Studio 2005, as shown in Figure 1.

Note The Microsoft.WindowsMobile.Status assembly relies on types that are defined in the Microsoft.WindowsMobile assembly; therefore, anytime you add a reference to Microsoft.WindowMobile.Status to a project, you must also add a reference to Microsoft.WindowsMobile. Failing to do so will result in build errors.

Figure 1. Adding a reference to the Microsoft.WindowsMobile.Status assembly. Click the thumbnail for a larger image.

If you want to spelunk through the contents of the assembly by using a tool like ildasm or Reflector (for more information, see the Lutz Roeder's Programming.NET Web site), you need to open the assembly file directly, which you can find in the Designtimereferences subfolder of the Windows Mobile 5.0 SDK installation folder. All classes that are contained in the Microsoft.WindowsMobile.Status assembly are part of the "Microsoft.WindowsMobile.Status" namespace.

Native-code developers access the State and Notifications Broker API functions and structures by including the regext.h header file. This header file and other Windows Mobile 5.0 header files are located in the \Include\Armv4i subfolder of the Windows Mobile 5.0 SDK installation folder. The State and Notifications Broker API functions are all implemented in the aygshell.lib library. Visual Studio 2005 generated projects that target the Windows Mobile 5.0 platform automatically link to this library, so in most cases, you won't have to add the aygsell.lib file to the project libraries—because it will already be there.

Retrieving Windows Mobile State Information

As I mentioned previously in this article, one role of the State and Notifications Broker API is to provide unified access to state information. This information provides one-stop shopping for all of your system state needs. The Windows Mobile 5.0 platform ships with over 100 state values that the State and Notifications Broker tracks. These values relate to both the state of the device itself and the state of standard Windows Mobile 5.0–based applications such as Pocket Outlook.

Examples of system-wide state values include the following:

  • ActiveSync status
  • Whether a camera is present
  • Number of Bluetooth connections
  • List of current network connections
  • Whether a headset is connected
  • Whether a keyboard is connected
  • Display orientation
  • Battery strength
  • Outlook Mobile contact that corresponds to the current caller on the phone

Examples of per-user state values include the following:

  • Names of the currently active and previously active applications
  • Next calendar appointment
  • Media player information including current track title, artist, and album
  • Name of the Outlook Mobile e-mail account and number of unread e-mail messages
  • Name of short message service (SMS) account and number of unread SMS messages
  • Number of missed phone calls
  • Number of active tasks and number of overdue tasks

As you can see, the list of state information that the State and Notifications Broker tracks is quite comprehensive. In addition to more than 100 standard state values, original equipment manufacturers (OEMs) are free to add more values.

If you've been working with Windows Mobile–based devices for a while, you might notice that several of the listed state values are available from other APIs. For example, you can determine the current display orientation by calling the GetSystemMetrics function or by checking the Screen.PrimaryScreen.Bounds property if you are using managed code. Similarly, you can retrieve the current battery strength by calling the GetSystemPowerStatusEx method. And of course, information regarding the Calendar and Tasks is available through the Pocket Outlook API.

With these existing APIs, you may wonder why the State and Notifications Broker API has been added. As you'll see, there are a number of reasons, but one of the most notable is that the State and Notifications Broker API provides a single source for retrieving all state information. It is no longer necessary to hunt down a separate function or API for each individual state value. Also, prior to the introduction of the State and Notifications Broker API, determining a specific state value often required several function calls—and sometimes additional logic. With the State and Notifications Broker API, each state value is available through a single function call (in the case of native code) and as a single property value (in managed code).

The underlying implementation of the State and Notifications Broker uses the registry as the data store. All system-wide state values are stored under the HKEY_LOCAL_MACHINE\System\State registry key, and per-user state information is stored under the HKEY_CURRENT_USER\System\State registry key.

Under each of these registry keys, there are multiple child keys. Each child key has one or more values. These values hold the actual state information. Figure 2 shows the contents of the HKEY_LOCAL_MACHINE\System\State registry key on the Windows Mobile 5.0 Smartphone emulator.

Figure 2. State registry keys on the Windows Mobile 5.0 Smartphone emulator. Click the thumbnail for a larger image.

Accessing State Values from Native Code

With the information for the State and Notifications Broker being stored in the registry, you can easily access the information by using standard registry functions like RegOpenKeyEx and RegQueryValueEx. The following code example shows how to retrieve the name of the phone's service operator with native code by using these functions.

HKEY hPhone;
TCHAR tszOperatorName[1024];
DWORD dwType;
DWORD dwSize = sizeof(tszOperatorName);

RegOpenKeyEx(HKEY_LOCAL_MACHINE, _T("System\\State\\Phone"),
             0, 0, &hPhone);
RegQueryValueEx(hPhone, _T("Current Operator Name"), 0, &dwType,
               (BYTE*)tszOperatorName, &dwSize);

This code uses the RegOpenKeyEx function to open the HKEY_LOCAL_MACHINE\System\State\Phone registry key and then retrieves the operator name from the key's Current Operator Name value using the RegQueryValueEx function.

If you've done some work with the registry, you know that this pattern of opening a key and then accessing a value is common. With the introduction of the State and Notifications Broker, this pattern of access will likely become even more common. As a result, to simplify accessing registry values, the State and Notifications Broker API introduces two new functions: RegistryGetString and RegistryGetDWORD. Each function encapsulates both opening a key and accessing a value. The two functions behave exactly the same; they are just specialized for retrieving either string or DWORD values. The following example retrieves the phone service operator this time by using the RegistryGetString function.

TCHAR tszOperatorName[1024];
RegistryGetString(HKEY_LOCAL_MACHINE, _T("System\\State\\Phone"), 
    "Current Operator Name", tszOperatorName, sizeof(tszOperatorName));

As you can see, this code example is much simpler than the previous code example because it requires only a single function call and avoids the need to declare the extra variables that the RegOpenKeyEx and RegQueryValueEx functions require.

One of the biggest challenges in working with the State and Notifications Broker API in native code is keeping track of all of the registry key and value names. With more than 100 standard state values in the State and Notifications Broker, there are also more than 100 registry key and value name combinations. You also need to keep track of whether the registry key that corresponds to the state value of interest is located under the HKEY_LOCAL_MACHINE or HKEY_CURRENT_USER registry key. Thankfully, you can find the list of standard state values that the State and Notifications Broker API tracks and the corresponding registry information in the State and Notifications Broker Base Properties help topic within the mobile5sdk.chm help file, which is located in the Help subfolder of the Windows Mobile 5.0 SDK installation folder.

Note The easiest way to locate the help topic is to open the mobile5sdk.chm file and search for the string "State and Notifications Broker Base Properties". At the time I wrote this article, there is a version of the State and Notifications Broker Base Properties help topic located in the MSDN Library, but it didn't include the registry key and value names.

The documentation is a great help, but with the registry key names and value names being strings, there's no way to know about any error that might exist in the registry key or value name strings at compile time. Instead, the program will fail at run time. To avoid this failure, the State and Notifications Broker API provides a header file, snapi.h, that provides a complete list of #define values for the standard State and Notifications Broker API state values. The #define values include the root key, the key name, and value name for each state value. The following code example is the portion of the snapi.h header file that corresponds to the phone service operator state value.

///////////////////////////////////////////////////////////////////////
// PhoneOperatorName
// Gets the name of the mobile operator (i.e., the mobile phone 
// company, or carrier).
#define SN_PHONEOPERATORNAME_ROOT HKEY_LOCAL_MACHINE
#define SN_PHONEOPERATORNAME_PATH TEXT("System\\State\\Phone")
#define SN_PHONEOPERATORNAME_VALUE TEXT("Current Operator Name")

The following code example shows accessing the phone service operator, this time by using the provided #define values instead of the literal strings.

TCHAR tszOperatorName[1024];
RegistryGetString(SN_PHONEOPERATORNAME_ROOT, SN_PHONEOPERATORNAME_PATH, 
    SN_PHONEOPERATORNAME_VALUE, tszOperatorName, 
    sizeof(tszOperatorName));

Using the #define values rather than literal strings makes the code easier to read and, in the event that there are any errors in the #define value names, the compiler reports them.

Working with DWORD Values and Bitmasks in Native Code

When accessing DWORD state values you use the RegistryGetDWORD function rather than the RegistryGetString function of course, but there's also a little more to it. Let's take a look at the #defines values for two state values in the snapi.h header file: Phone Multi-Line and Phone Radio Present.

///////////////////////////////////////////////////////////////////////
// PhoneMultiLine
// Gets a value indicating whether the phone supports multiple lines.
#define SN_PHONEMULTILINE_ROOT HKEY_LOCAL_MACHINE
#define SN_PHONEMULTILINE_PATH TEXT("System\\State\\Phone")
#define SN_PHONEMULTILINE_VALUE TEXT("Multiline Capabilities")

///////////////////////////////////////////////////////////////////////
// PhoneRadioPresent
// Gets a value indicating whether the mobile device has a phone.
#define SN_PHONERADIOPRESENT_ROOT HKEY_LOCAL_MACHINE
#define SN_PHONERADIOPRESENT_PATH TEXT("System\\State\\Phone")
#define SN_PHONERADIOPRESENT_VALUE TEXT("Status")
#define SN_PHONERADIOPRESENT_BITMASK 32

In the case of Phone Multi-Line, a single function call accesses the DWORD value very much like accessing a string state value, as shown in the following code example.

DWORD dwPhoneMultiLine;
RegistryGetDWORD(SN_PHONEMULTILINE_ROOT, SN_PHONEMULTILINE_PATH, 
    SN_PHONEMULTILINE_VALUE, &dwPhoneMultiLine);

By looking at the Phone Multi-Line #define values, you can see that the state value is stored in the MultiLine Capabilities value of HKEY_LOCAL_MACHINE\System\State\Phone. A quick look at Figure 3 shows that the state value is 1. As a result, the call to the RegististryGetDWORD function in the previous code example returns 1, indicating that the phone on the current device—in this case is the Windows Mobile 5.0 Smartphone emulator—supports multiple lines.

Figure 3. The Phone state values viewed with regedit.exe. Click the thumbnail for a larger image.

Note In the case of the Windows Mobile 5.0 Smartphone emulator, the radio is a fake radio with service provided by Fake Network. From a programming standpoint, this emulator provides all of the characteristics of having a phone radio, so attempts to make a phone call or to check the status of the radio appear to succeed when, in reality, no phone actually exists. This fake radio is very useful for developing and initial testing of phone-related applications without incurring the cost of placing a mobile phone call or acquiring a device.

For the Phone Radio Present state value, the #define values show that it is stored in the registry value Status under the same key as Phone Multi-Line. If you look at Figure 3 again, it shows that the value stored in the registry for Status is 9437344. This value may look strange and certainly doesn't seem to apply to whether or not the device has a radio.

Unlike the registry value MultiLine Capabilities, which stores a single state value, Status actually stores multiple state values, and each state value is represented as a separate bit. To access the specific state value of interest, you must apply a bitmask. In the case of the Phone Radio Present state value, that bitmask is contained in the #define value SN_PHONERADIOPRESENT_BITMASK. The following code example shows using the bitmask to determine if the current device has a phone radio.

DWORD dwValue;
RegistryGetDWORD(SN_PHONERADIOPRESENT_ROOT, SN_PHONERADIOPRESENT_PATH, 
                 SN_PHONERADIOPRESENT_VALUE, &dwValue);
  BOOL bPhoneRadioPresent = dwValue & SN_PHONERADIOPRESENT_BITMASK;

As you can see, the initial value is still retrieved from the registry by using the RegistryGetDWORD function, but the additional step of applying the bitwise AND operator to the returned value is required to see if the bit corresponding to the state value Phone Radio Present is set.

Not all DWORD state values that require a bitmask are Boolean values. In some cases, a single DWORD is used to store two smaller ranged state values—as in the case of the Main registry value under HKEY_LOCAL_MACHINE\System\Status\Battery. This one registry value represents both the battery strength level and the battery state. These two state values also demonstrate another important issue: these values are enumerations. As enumerations, they each represent a finite set of values. The thing that I find frustrating is that the current implementation of the State and Notifications Broker API doesn't provide any corresponding C/C++ enum declarations or #define values. So you can either place the literal values in your code—leaving anyone who must later support that code to figure out what they mean—or you can provide your own #define declarations. The mobile5sdk.chm help file topic, State and Notifications Broker Base Properties, that I mentioned previously in this article contains the list of possible values for these two state values. The following code example contains #define values that you can use with the battery state and battery strength level state values.

#define BATTERYLEVEL_VERYLOW    0
#define BATTERYLEVEL_LOW       21
#define BATTERYLEVEL_MEDIUM    41
#define BATTERYLEVEL_HIGH      61
#define BATTERYLEVEL_VERYHIGH  81

#define BATTERYSTATE_NORMAL     0
#define BATTERYSTATE_NOTPRESENT 1
#define BATTERYSTATE_CHARGING   2
#define BATTERYSTATE_LOW        4
#define BATTERYSTATE_CRITICAL   8

Using these #define values, the following code example demonstrates accessing the battery state and battery strength level state values.

DWORD dwBatteryMain;
RegistryGetDWORD(SN_POWERBATTERYSTRENGTH_ROOT, 
                 SN_POWERBATTERYSTRENGTH_PATH, 
                 SN_POWERBATTERYSTRENGTH_VALUE, 
                 &dwBatteryMain);

int batteryLevel = (dwBatteryMain & SN_POWERBATTERYSTRENGTH_BITMASK) >> 16;
int batteryState = dwBatteryMain & SN_POWERBATTERYSTATE_BITMASK;

TCHAR tszBatteryLevel[256];
// Build a string description of the battery strength level
switch(batteryLevel)
{
    case BATTERYLEVEL_VERYLOW :
        _tcscpy(tszBatteryLevel, _T("Battery Level: Very Low"));
        break;
    case BATTERYLEVEL_LOW :
        _tcscpy(tszBatteryLevel, _T("Battery Level: Low"));
        break;
    case BATTERYLEVEL_MEDIUM :
        _tcscpy(tszBatteryLevel, _T("Battery Level: Medium"));
    case BATTERYLEVEL_HIGH :
        _tcscpy(tszBatteryLevel, _T("Battery Level: High"));
    case BATTERYLEVEL_VERYHIGH :
        _tcscpy(tszBatteryLevel, _T("Battery Level: Very High"));
        break;
}

TCHAR tszBatteryState[256];
// Build a string description of the battery state
_tcscpy(tszBatteryState, _T("Battery State: "));
if (batteryState & BATTERYSTATE_NORMAL)
    _tcscat(tszBatteryState, _T("Normal "));
if (batteryState & BATTERYSTATE_NOTPRESENT)
    _tcscat(tszBatteryState, _T("Not Present "));
if (batteryState & BATTERYSTATE_CHARGING)
    _tcscat(tszBatteryState, _T("Charging "));
if (batteryState & BATTERYSTATE_LOW)
    _tcscat(tszBatteryState, _T("Low "));
if (batteryState & BATTERYSTATE_CRITICAL)
    _tcscat(tszBatteryState, _T("Critical "));

This code example demonstrates several simple—but important—points. The first point is the opportunity to optimize the retrieval of state values when they share a single registry value. After the RegistryGetDWORD function is called to retrieve the registry value Main for the HKEY_LOCAL_MACHINE\System\State\Battery registry key, applying the respective bitmasks determines both the battery state and battery strength level state values. Reusing the registry value is obviously more efficient than making a second call to the RegistryGetDWORD function by using the SN_POWERBATTERYSTATE_XX #define values, which would return the same registry value that the call using the SN_POWERBATTERYSTRENGTH_XX #define values returned. This optimization is especially helpful in cases where the registry value represents a large number of state values. For example, the Status registry value for the HKEY_LOCAL_MACHINE\System\State\Phone registry key I discussed previously in this article actually holds 25 different state values—meaning that a single call to the RegistryGetDWORD function can retrieve one DWORD value that contains the 25 individual values. Each individual value is determined by simply using the bitwise AND operator to apply the appropriate #define value for each.

The second point demonstrated in the code example is the importance of knowing the value of the bitmask used to access the state value. In the case of battery strength level, the bitmask SN_POWERBATTERYSTRENGTH_BITMASK is 0xFFFF0000, indicating that the value is stored in the upper two bytes of the registry value. To make the result of the bitwise AND operator meaningful, you must shift the value 16 bits (two bytes) to the right. For example, if the result of the bitwise AND is 0x150000 (1,376,256 decimal), shifting the value 16 bits produces 0x15 (21 decimal). Checking the battery strength level #define values tells us that a battery strength level of 21 decimal means that the battery strength level is low.

The third point from the code example has to do with the knowing whether the state enumeration values are simple values or if these values also require the use of a bitmask. In the case of the battery strength level, the state value is a simple value, meaning that of the five #defines values, the battery strength level matches exactly one. With this being the case, the easiest way in the code example to check the current value is to simply use a switch statement. In the case of the battery state, you must use a bitmask to determine the state value itself because multiple battery states may be true at the same time; for example the battery may be charging, but it may still be low. As a result, the code example explicitly checks for each battery state by using a bitwise AND operator.

Accessing State Values from Managed Code (or Managed Code is Bliss)

As is usually the case, using C or C++ gives a developer the most direct access to the behaviors and capabilities of the platform. As is also usually the case, the cost of this direct access is that the C/C++ developer is responsible to handle the details involved in interacting with the platform. On the other hand, managed code puts a greater focus on developer productivity by choosing to encapsulate details within class libraries. I don't know that I've ever seen a case where the difference between these two philosophies is more evident than in the State and Notifications Broker API.

For managed-code developers, static properties in the SystemState class provide access to the standard State and Notifications Broker state values. Whether you are accessing the State and Notifications Broker API from native or managed code, the underlying implementation is identical and the same registry structure and values are being used. The one important difference is that all of the details of interacting with the registry including specific registry locations and the required bitmasks are completely encapsulated in the SystemState class. Managed-code developers simply access the SystemState static property that corresponds to the state value of their interests. The following code example demonstrates accessing the Phone Service Operator Name, Phone Multi-Line Capabilities, and Phone Radio Present state values.

using Microsoft.WindowsMobile.Status;
// …string operatorName = SystemState.PhoneOperatorName;
bool phoneMultiLine = SystemState.PhoneMultiLine;
bool phoneRadioPresent = SystemState.PhoneRadioPresent;

As you can see by the code example, the SystemState properties completely encapsulate the registry and bitmask details. Each property simply accesses the corresponding state value.

In addition to encapsulating the details of accessing the state values, the managed State and Notifications Broker API also makes interpreting the returned state values easier by providing .NET Compact Framework enumerated types for those properties that have enumerated return values, as demonstrated by the following code example.

BatteryLevel batteryLevel = SystemState.PowerBatteryStrength;
BatteryState batteryState = SystemState.PowerBatteryState;

string batteryLevelText;
switch (batteryLevel)
{
    case BatteryLevel.VeryLow:
        batteryLevelText = "Battery Level: Very Low";
        break;
    case BatteryLevel.Low:
        batteryLevelText = "Battery Level: Low";
        break;
    case BatteryLevel.Medium:
        batteryLevelText = "Battery Level: Medium";
        break;
    case BatteryLevel.High:
        batteryLevelText = "Battery Level: High";
        break;
    case BatteryLevel.VeryHigh:
        batteryLevelText = "Battery Level: Very High";
        break;
}

string batteryStateText = "Battery State: ";
if ((batteryState & BatteryState.Normal) == BatteryState.Normal)
    batteryStateText += "Normal";
if ((batteryState & BatteryState.NotPresent) == BatteryState.NotPresent)
    batteryStateText += "Not Present";
if ((batteryState & BatteryState.Charging) == BatteryState.Charging)
    batteryStateText += "Charging";
if ((batteryState & BatteryState.Low) == BatteryState.Low)
    batteryStateText += "Low";
if ((batteryState & BatteryState.Critical) == BatteryState.ChargCriticaling)
    batteryStateText += "Critical";

As you can see, the type of the SystemState.PowerBatteryStrength property is BatteryLevel, and the type of the SystemState.PowerBatteryState property is BatteryState making it very clear what to expect from each property. Notice that the way the enumeration values are tested in managed code is consistent with the same tests in native code; the BatteryLevel values are tested against specific values, whereas the BatteryState values are tested with a bitwise AND operator.

By encapsulating the details of the registry representation, the managed implementation of the State and Notifications Broker API makes accessing the standard state values extremely easy. This doesn't mean that managed-code developers never need to understand the registry structure. When we look at some of the more advanced uses of the State and Notifications Broker API next month, you will find that solidly understanding the State and Notifications Broker registry structure is just as important for managed-code developers as native-code developers.

Receiving Notifications

Acting as a central repository, the State and Notifications Broker certainly makes retrieving system and application information much easier than it was before the State and Notifications Broker API. Centralized state information is a great start, but for our applications to effectively cooperate with the device and the other applications running on those devices, we need to be able to do more than simply retrieve state values; we also need to be notified of changes to the state values. It's these notifications that transform the Windows Mobile platform from a bunch of independent applications into a holistic and cooperative platform of applications that work together as a single meta-application.

Note The notifications feature of the State and Notifications Broker is extremely rich and flexible; however, I've just about used up all of the space I have for this month's column, so we'll focus on notification basics for now. In next month's column, we'll dig deep into the different notification features and capabilities.

Receiving Notifications in Native Code

change notifications, your application must perform three distinct steps:

  • Register to be notified about changes in one of the state values.
  • Handle the notification event.
  • Retrieve the new state value.

We'll look at how receiving notifications works by using one of the most important notifications to a mobile device application—the number of network connections. If the value is greater than zero, the device has a network connection and, therefore, can perform network-based communications.

The RegistryNotifyWindow function is the simplest way in native code to register for change notifications. The function accepts the registry key and value name of the monitored state value in addition to a window message identifier (ID) and a window handle. As the monitored state value changes, the State and Notifications Broker sends the specified message ID to the window identified by the handle.

The window message ID can be any valid message ID, but it is usually an ID value that is created by calling the RegisterWindowMessage function or by defining a constant based on the WM_USER #define value. For simplicity, we'll define a constant as shown in the following code example.

const DWORD WM_CONNECTIONSNETWORKCOUNT = WM_USER + 1;

Note If you are new to creating custom window messages or haven't created one in a while, you can find more information about the importance of the WM_USER #define value from the MSDN documentation.

With the message defined, we are now ready to call the RegistryNotifyWindow function. You can call the RegistryNotifyWindow function at almost any point in the application you like—as long as the window represented by the window handle has been created. In this case, we want the notification sent to the main application window, so we can register in either the create (WM_CREATE) message handler or the InitInstance function.

The following code example shows calling the RegistryNotifyWindow function from the InitInstance function.

// Global Notification Handle
HREGNOTIFY g_hNotify = NULL;

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    // ...
    // ...

    g_hWnd = CreateWindow(szWindowClass, szTitle, WS_VISIBLE,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
        NULL, NULL, hInstance, NULL);
    ShowWindow(g_hWnd, nCmdShow);
    UpdateWindow(g_hWnd);

    // Register for changes in the number of network connections
    HRESULT hr = RegistryNotifyWindow(SN_CONNECTIONSNETWORKCOUNT_ROOT,
        SN_CONNECTIONSNETWORKCOUNT_PATH, SN_CONNECTIONSNETWORKCOUNT_VALUE, 
        g_hWnd, WM_CONNECTIONSNETWORKCOUNT, 0, NULL, &g_hNotify);

    return TRUE;
}

Notice that the last parameter to the RegistryNotifyWindow function is a returned handle to the notification. The handle is used to close the notification when it is no longer needed. For easy access later, the application stores the handle in a global variable.

To handle the notification, we simply modify the switch statement that is contained in the window's WndProc function, so the switch statement includes a case condition for the notification's registered message ID. Depending on the data type of the value being monitored, the new data value may or may not be included in the message. For values that are stored in the registry as the REG_DWORD data type (which include all numeric and enumeration values), the new value is passed in the message WPARAM parameter. For string values, the application must call the RegistryGetString function to retrieve the new value. In the case of this example, the Connections Network Count state value is numeric and, therefore, the new value is contained in the WPARAM parameter of the message.

The following code example shows the relevant portions of the WndProc function to handle the notification.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int nNetworkConnections;
    int wmId, wmEvent;
    
    switch (message) 
    {
        // Number of network connections changed
        case WM_CONNECTIONSNETWORKCOUNT:
            nNetworkConnections = (int) wParam;
            if (nNetworkConnections == 0)
                StopDataTransfer();
            else
                StartOrContinueDataTransfer();
            break;
        case WM_COMMAND:
            // ..
        case WM_PAINT:
            // ..
        case WM_CREATE:
            // ..
        case WM_DESTROY:
            // ..
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}

That's all there is to it—the application now responds to changes in available network connectivity. Just as the code example demonstrates, an application can monitor changes in any of the state values by simply registering to receive the notification, handling the notification, and retrieving the current value that, in many cases, is passed as part of the message.

One last thing to remember is that monitoring notifications consumes device resources, so you'll want to call the RegistryCloseNotification function when you no longer need the notification or when the application closes. The only argument to the RegistryCloseNotification function is the handle returned by calling the RegistryNotifyWindow function.

In many cases, you will want the notification for the life of the application, so the most logical place to close the notification handle is when the application closes, as shown in the following code example.

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    int nNetworkConnections;
    int wmId, wmEvent;
    
    switch (message) 
    {
        case WM_CONNECTIONSNETWORKCOUNT:
            // ..
        case WM_COMMAND:
            wmId    = LOWORD(wParam); 
            wmEvent = HIWORD(wParam); 
            switch (wmId)
            {
                case IDM_EXIT:
                    // Close notification handle
                    if (g_hNotify)
                        RegistryCloseNotification(g_hNotify);

                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
            }
        case WM_PAINT:
            // ..
        case WM_CREATE:
            // ..
        case WM_DESTROY:
            // ..
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }

    return 0;
}

Receiving Notifications in Managed Code

As in the case of retrieving values from the State and Notifications Broker, the .NET Compact Framework encapsulates many of the details that are involved in state value change notifications within the SystemState class. In addition to providing the many static properties that are used to access the state values, the SystemState class also provides a Changed instance-based event. To receive a state value change notifications, your application must perform the following two steps:

  • Create an instance of the SystemState class and pass the appropriate SystemProperty enumeration that identifies the value of your interest.
  • Attach a delegate to the new SystemState instance's Changed event.

After these steps are complete, the delegate will be called with each change in the state value.

The following code example shows the C# code to register for notification of changes in the Connections Network Count value.

public partial class FormMain : Form
{
    private void FormMain_Load(object sender, EventArgs e)
    {
         _connectionsNetworkCount = 
               new SystemState(SystemProperty.ConnectionsNetworkCount);
        _connectionsNetworkCount.Changed+=connectionsNetworkCount_Changed;
    }
    // ...
    // ...
}
    SystemState _connectionsNetworkCount;

The _connectionsNetworkCount instance invokes its Changed event on any change in the Connections Network Count state value, which then calls the connectionsNetworkCount_Changed method.

Implementing the connectionsNetworkCount_Changed method is very simple, as shown in the following code example.

void _connectionsNetworkCount_Changed(object sender, 
        ChangeEventArgs args)
{
    int connectionsCount = (int)args.NewValue;
    if (connectionsCount == 0;
        StopDataTransfer();
    else
        StartOrContinueDataTransfer();
}

The new state value is contained in the NewValue property of the ChangeEventArgs parameter. Unlike native code where the new value is only passed in the case of REG_DWORD typed values, managed code always passes the new value to the delegate. The ChangeEventArgs.NewValue property is of course declared as type object and must, therefore, be cast to the appropriate type as shown in the previous code example.

The only remaining consideration is releasing the resources that are associated with monitoring the state value. Like other classes in the .NET Compact Framework that hold unmanaged resources, the SystemState class implements the IDisposable interface and, therefore, has a Dispose method to release those resources.

The following code example releases the SystemState resources as part of the form's Closing event.

private void FormMain_Closing(object sender, CancelEventArgs e)
{
    if (_connectionsNetworkCount != null)
    {
        _connectionsNetworkCount.Dispose();
        _connectionsNetworkCount = null;
    }
}

Conclusion

You now know all you need to take advantage of the State and Notifications Broker as the central source for all device information—whether that information is related to the device itself or to the standard Windows Mobile 5.0 applications. By using the State and Notifications Broker API, you can easily retrieve the current contents of one of the state values and, if you want, you can have the system notify you of changes in that value.

The addition of the State and Notifications Broker opens up a whole new world of device application development. Device applications should not be an independent island unto themselves. With the State and Notifications Broker, you can now build your applications to interact much more closely with the system and the other applications on the system to provide the user with a much more holistic experience.

That does it for this month, but there are still many great State and Notifications Broker features we haven't gotten to yet. Please join me again next month as we cover some of the more advanced State and Notifications Broker features and show how you can integrate your own application's values into the system.