free web page counters

Windows Mobile Pocket PC Smartphone Programming

==>Click here for the SiteMap<==. Original contents with decent amount of source codes.

Wednesday, November 09, 2005

Pocket PC Power Management Series 4: A Better Way to Catch when the Device Wakes Up

====>SiteMap of this Blog<===

Pocket PC Power Management Series 4: A Better Way to Catch when the Device Wakes Up

In Pocket PC Power Management Series 3, I presented a simple program that depicts when the device runs and when it sleeps. The program uses a rudimentary and inefficient way to achieve the purpose: It spawns a periodically sleeping thread, which is responsible for recording whether itself sleeps through the predefined time frame. If it sleeps longer than expected, it draws the conclusion that the device must have been suspended.

The recommended and efficient way is to use the omnipotent CeSetUserNotificationEx API. If you happen to write any serious Windows Mobile programs, you must either have heard about it, or have used it. Basically it allows the developer to either launch a program, or to display a dialog, or to emit a sound, or to vibrate the device, if certain predefined system events occur, or if the predefined time arrives. I believe the Alarm program shipped with any Pocket PC or Smartphone (for example, the devices I have at hand: Sprint SmartDevice 6600, Sprint SmartDevice 6700, Palm Treo 700w, Cingular 8125, I-Mate SP5, Cingular MPx220, Sprint Samsung i600) must be written based on this API.

Luckily enough, Microsoft includes device wakeup event as one of the system events. The way to make use of the event is a little tweaked, so here is my example and source code, with detailed explanations.

1. Two helper functions to deal with wakeup Notifications: AddWakeupNotification and ClearWakeupNotification

AddWakeupNotification anticipates a constant string, which is the event name. Such name is also used later as the name of an event, as described in msdn documentation for CeSetUserNotificationEx and CE_NOTIFICATION_TRIGGER.

One notorious side-effect of CeSetUserNotificationEx is that the notification will be persisted in a central repository, unless the creator remembers to remove it. I've seen a couple of novice developers forgot to clear the notifications and subject his/her testing device to some really weird behaviors. So I also present ClearWakeupNotification, which also accepts a constan string.

static HRESULT AddWakeupNotification(LPCTSTR szEventName)
{
  HRESULT hr = S_OK;
  HANDLE hNotify = NULL;

  // set a CE_NOTIFICATION_TRIGGER
  CE_NOTIFICATION_TRIGGER notifTrigger;
  memset(&notifTrigger, 0, sizeof(CE_NOTIFICATION_TRIGGER));
  notifTrigger.dwSize = sizeof(CE_NOTIFICATION_TRIGGER);

  TCHAR szNamedEventName[MAX_PATH];
  memset(szNamedEventName, 0, sizeof(TCHAR)*MAX_PATH);
  _tcscat(szNamedEventName, NAMED_EVENT_PREFIX_TEXT);
  _tcscat(szNamedEventName, szEventName);

  notifTrigger.dwType = CNT_EVENT;
  notifTrigger.dwEvent = NOTIFICATION_EVENT_WAKEUP;
  notifTrigger.lpszApplication = szNamedEventName;
  //notifTrigger.lpszArguments = NULL;

  // set the notification
  hNotify = CeSetUserNotificationEx(0, &notifTrigger, NULL);
  // NULL because we do not care what action to take

  if (!hNotify) {
   hr = E_FAIL;
  } else {
   // close the handle as we do not need to use it further
   CloseHandle(hNotify);
  }

  return hr;
}

static HRESULT ClearWakeupNotification(LPCTSTR szEventName)
{
  HRESULT hr = S_OK;

  TCHAR szToCompare[MAX_PATH];
  memset(szToCompare, 0, sizeof(TCHAR)*MAX_PATH);
  _tcscat(szToCompare, NAMED_EVENT_PREFIX_TEXT);
  _tcscat(szToCompare, szEventName);

  // hold a notification
  PBYTE pBuff = (PBYTE)LocalAlloc(LPTR, 8192);

  if (!pBuff) {
      return E_OUTOFMEMORY;
  }

  // at most 256 notification handles -
  // should be more than enough for a typical device
  HANDLE hNotifHandlers[256];
  DWORD nNumHandlers = 0;
  DWORD i = 0;
  int rc = CeGetUserNotificationHandles(hNotifHandlers,
  dim(hNotifHandlers), &nNumHandlers);
  if (!rc) {
      hr = E_FAIL;
      goto FuncExit;
  }

  // iterate all notifications
  // Notice: We do not care about the status of the notification.
  // Just clear it even if it is not filed??
  for (; i<nNumHandlers; i++) {
      // query info for this specific handler
      BOOL bClearThis = FALSE;
      DWORD dwSize = 0;
      rc = CeGetUserNotification(hNotifHandlers[i], 8192, &dwSize, pBuff);
      if (!rc) continue;
  
      PCE_NOTIFICATION_INFO_HEADER pnih = (PCE_NOTIFICATION_INFO_HEADER)pBuff;
      PCE_NOTIFICATION_TRIGGER pNotifTrigger = pnih-&gt;pcent;
  
      // Notice some events with NULL lpszApplication might be inserted!
      if (pNotifTrigger && pNotifTrigger->lpszApplication
          && !_tcsicmp(pNotifTrigger->lpszApplication, szToCompare)) {
          CeClearUserNotification(pnih->hNotification);
      }
  }

FuncExit:
  if (pBuff) {
  LocalFree(pBuff);
  }

  return hr;
}

2. Wait for the named event

In Windows, events are named. What we need to do here is to create such a named event, and request that the notification subsystem set the named event when the notification event occurs. So the code skeleton is like this:

HRESULT hr = AddWakeupNotification(g_szEventName);
HANDLE hWakeupEvent = CreateEvent(NULL, TRUE, FALSE, g_szEventName);

// some code to wait for the named event
// -- meaning the device just wakes up
// ...

CloseHandle(hWakeupEvent);
hr = ClearWakeupNotification(g_szEventName);

The tricky part is how to wait for the wakeup event. If you really hates multi-threaded programming, I have the following single-thread example for your reference:


HANDLE eventHandles[] = {hWakeupEvent};
DWORD dwEventCount = 1;

TCHAR szBuf[256];
MSG currentMsg;

while (true) {
   ResetEvent(hWakeupEvent);
   DWORD dwRC = MsgWaitForMultipleObjectsEx(dwEventCount,
                   eventHandles, INFINITE, QS_ALLEVENTS, NULL);
   if (WAIT_OBJECT_0 == dwRC) {
       ALERT(TEXT("Aha Wakeup just now!));
   } else if (WAIT_FAILED == dwRC) {
       // XXX wait failed?
   } else if (WAIT_OBJECT_0+dwEventCount == dwRC) {
       int nGetMsg = GetMessage(&currentMsg, NULL, 0, 0);
       if (nGetMsg > 0) {
           TranslateMessage(&currentMsg);
           DispatchMessage(&currentMsg);
       } else if (nGetMsg < 0) {
           // XXX GetMsg failed;
       } else {
           break;
       }
   }
}

Read the above code, you'll find the familiae bolier plate code TranslateMessage() and DispathMessage(). Yes it is right! You can use MsgWaitForMultipleObjectsEx to both respond to the current thread message queue and listen to a list of events. In Windows, a window is associated with a thread, the thread that creates the window. So the "thread message queue" actually is your familiar "windows message queue". Use the powerful MsgWaitForMultipleObjectsEx API, you can achieve the purpose using only one thread to deal with UI, and listen to the wakeup event.

However, the above technique might not work well, and you may notice you'll frequently miss a wake-up event. The reason is obvious. The single thread most probably is processing a windows message (remember the two function calls: TranslateMessage and DispathMessage). If this thread is executing a windows procedure (because of a windows message) when the device wakes up, the thread for sure will miss the wakeup signal!

So a reliable way is to spawn a new thread, and use WaitForSingleObject to listen to the wakeup signal. Of course this thread does not necessary only deal with the wakeup signal. You can make it run other errands by calling WaitForMultipleObjects, but just make sure it should not be a busy thread!


Update: I uploaded series 10 with the two-thread code. The Visual Studio 2005 solution can be downloaded as well.

Category: [Power Management]

====>SiteMap of this Blog<===