Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Windows Mobile Widget 101 – System State (aka Notification Broker) and Device Information

Prior posts in this series:

Whilst Windows Mobile widgets are great, they definitely have their shortcomings when compared for true mobile application. One such area is in the ability to interact with device hardware and information. Further, it appears that every OS provider seems to be tackling this differently.  If you look at S60 they have expose a wealth of device and user information via their widget API. Unfortunately one of the weaknesses of the current Microsoft implementation of widgets is that there are only a few pieces of device information you can query (out of the box) and no user information (ie contacts, calendar…). If you want to do more, you have to build and deploy ActiveX controls to assist your widget.

Returning to the Windows Mobile widgets and out of the box you get the following pieces of system information. All are exposed via the SystemState object.

CradlePresent

A Boolean value indicating whether the device is cradled.

DisplayRotation

An integer value indicating whether the display is in portrait or landscape mode.

PhoneHomeService

A Boolean value indicating whether the device is presently registered on its home network.

PhoneOperatorName

A string indicating the name of the device’s mobile operator.

PhoneRoaming

A Boolean value indicating whether the device is currently roaming.

PhoneSignalStrength

An integer indicating the phone signal strength, expressed as a percentage of full strength.

PowerBatteryState

An integer indicating the current state of the battery, such as whether the battery level is critically low or if the battery is charging.

PowerBatteryStrength

An integer indicating the current battery strength, expressed as a percentage of full strength.

And here’s an example of how you would access one of these properties. You first need to create an instance of the SystemState object. Next, query the actual property you are interested. Finally, if you want to receive notification when that property changes, you need to add an event handler to the “changed” event, passing in the callback method as the second parameter.

var systemState = widget.createObject("SystemState");
var cradledState = systemState.CradlePresent;
cradledState.addEventListener("changed", cradled);

Note, that the SystemState properties and the callback event are all exposed via the Windows Mobile Notification Broker that has been around since Windows Mobile 5. What I don’t get is why only this limited set of properties?

One of the frustrating things about working with widgets is determining whether your widget has a connection, firstly to a network and secondly to your server. Whilst there is no “IsConnected” property exposed via the SystemState object, you can use a combination of properties to try and deduce if there is a network connection.

Lets start with the widget.js file. You have to be careful when working with the widget API to ensure you don’t break your cross-platform capabilities.  Of course if you don’t want your widget to run as a gadget you can eliminate quite a bit of the following code.

-----------------
widget.js
-----------------

var WidgetAPI = function () {
    return {
        // Code omitted as same as previous post
        systemState: null,
        stateEvents: null,
        createSystemState: function () {
            if (WidgetAPI.systemState != null) return;

            if (window.widget) {
                WidgetAPI.systemState = window.widget.createObject("SystemState");
            }
            else {
                WidgetAPI.systemState = {};
            }
        },
        getCradleState: function () {
            WidgetAPI.createSystemState();

            if (!WidgetAPI.systemState.CradlePresent) {
                WidgetAPI.systemState.CradlePresent = true;
            }

            return WidgetAPI.systemState.CradlePresent;
        },

        listenForCradleStateChanged: function (callback) {
            WidgetAPI.listenForStateChanged(WidgetAPI.getCradleState(), callback);
        },

        getPhoneStrengthState: function () {
            WidgetAPI.createSystemState();

            if (!WidgetAPI.systemState.PhoneSignalStrength) {
                WidgetAPI.systemState.PhoneSignalStrength = 100;
            }

            return WidgetAPI.systemState.PhoneSignalStrength;
        },

        listenForPhoneStrengthStateChanged: function (callback) {
            WidgetAPI.listenForStateChanged(WidgetAPI.getPhoneStrengthState(), callback);
        },

        listenForStateChanged: function (state, callback) {
            if (window.widget) {
                state.addEventListener("changed", WidgetAPI.createSafeListenerCallback(callback));
            }
        },
        entered: false,
        createSafeListenerCallback: function (callback) {
            try {
                WidgetAPI.stateEvents = new Date();
                WidgetAPI.stateEvents.setTime(WidgetAPI.stateEvents.getTime() - 60 * 1000);
                var func = function () {
                    if (WidgetAPI.entered == true) return;
                    try {
                        WidgetAPI.entered = true;
                        var lastInvoke = WidgetAPI.stateEvents;
                        lastInvoke.setTime(lastInvoke.getTime() + 1 * 1000);
                        if (lastInvoke < (new Date())) {
                            WidgetAPI.stateEvents = new Date();
                            if (callback && callback != null) {
                                callback();
                            }
                        }
                    }
                    finally {
                        WidgetAPI.entered = false;
                    }
                };
                return func;
            }
            catch (e) {
                alert(e);
            }
        }

    };
} ();

The other thing you will notice about this code is that there is a wrapper used for calling the event callback. When certain system events are raised (eg PhoneSignalStrength) a number of events will be raised within a short period of time. This wrapper protects against this. Warning: Some events will be hidden by this wrapper, so only use it if you don’t mind if some events are missed. In our case we are only using the change in state to prompt the application to check if it still has connectivity.

In the utils.js file we’re going to add a simple Ping routine that can be used to detect if a server is reachable. It does so by downloading a specified image within a given timeframe. Note that you should use a small image as you don’t want to waste network bandwidth.

-----------------
Utils.js
-----------------

var Ping= {};
Ping = {
    img: null,
    imgPreload: null,
    timer: null,
    success: null,
    fail: null,
    hasFailed: false,
    init: function (imgUrl, pingCallback, failCallback) {
        Ping.img = imgUrl;
        Ping.success = pingCallback;
        Ping.fail = failCallback;

        Ping.imgPreload = new Image();
        Ping.hasFailed = false;
        Ping.imgPreload.onload = function () {
            clearTimeout(Ping.timer);
            Ping.timer = null;
            if (Ping.hasFailed == false && Ping.success != null) {
                Ping.success();
            }
        };

        Ping.imgPreload.src = Ping.img;
        if (Ping.fail != null) {
            Ping.timer = setTimeout(Ping.fail_to_ping, 10000);
        }
    },

    fail_to_ping: function () {
        clearTimeout(Ping.timer);
        Ping.timer = null;
        Ping.imgPreload = null;
        Ping.hasFailed = true;
        if (Ping.fail != null) {
            Ping.fail();
        }
    }
};

Now, to bring these together we’re going to introduce a new file, network.js, that registers for changes to the cradle and phone signal strength, and then based on those changes calls the Ping function to determine if the server can be reached. Note that as we don’t have any WiFi state information we can’t just assume that because the device is not cradled and there is no phone signal there is no data connection. As such, we just use the change in state to invoke ping the server. Again, there is some time checking around the Ping function to make sure it isn’t called too frequently.

----------------- 
Network.js
-----------------

var Network = function () {
    return {
        isConnected: false,
        connectionTestUrl: "
http://www.builttoroam.com/ping.gif",
        lastPing: null,
        minimumPingSeparationInSeconds: 30,
        connectionStateChanged: null,
        init: function (stateChangedCallback) {
            if (stateChangedCallback && stateChangedCallback != null) {
                Network.connectionStateChanged = stateChangedCallback;
            }

            WidgetAPI.listenForCradleStateChanged(Network.cradledStateChange);
            WidgetAPI.listenForPhoneStrengthStateChanged(Network.phoneStrengthChange);

            Network.testConnection();
        },

        cradledStateChange: function () {
            Network.testConnection();
        },
        phoneStrengthChange: function () {
            Network.testConnection();
        },

        testConnection: function () {
            var pingOk = false;
            if (Network.lastPing == null) {
                pingOk = true;
            }
            else {
                var pingTime = new Date();
                pingTime.setTime(Network.lastPing.getTime() + Network.minimumPingSeparationInSeconds * 1000);
                if (pingTime < (new Date())) {
                    pingOk = true;
                }
            }

            if (WidgetAPI.getCradleState() == false || WidgetAPI.getPhoneStrengthState() <= 0) {
                pingOk = true;
            }

            if (pingOk) {
                Network.lastPing = new Date();
                Ping.init(Network.connectionTestUrl, Network.connectionTestSuccessful, Network.connectionTestFailed);
            }
        },

        connectionTestSuccessful: function () {
            var isConnected = true;
            Network.setConnectedState(isConnected);
        },

        connectionTestFailed: function () {
            var isConnected = false;
            Network.setConnectedState(isConnected);
            Network.lastPing = null;
        },

        setConnectedState: function (connectedState) {
            if (Network.isConnected != connectedState && Network.connectionStateChanged != null) {
                Network.connectionStateChanged(connectedState);
            }

            Network.isConnected = connectedState;
        }
    };
} ();

Some minor changes to main.htm and main.js in order to display the connected state. Add a div, isConnected, to main.htm, and add a call to Network.init and a callback function to main.js.

-----------------
Main.htm
-----------------

<div id="isConnected">N/A</div>

-----------------
main.js
-----------------

function onLoad() {
    // Omitting code from previous post
    Network.init(connectionStateChanged);
}

function connectionStateChanged(isConnected) {
    var connected = $get("isConnected");
    if (isConnected == true) {
        connected.innerHTML = "Connected!";
    }
    else {
        connected.innerHTML = "Not connected!";
    }
}
 

After deploying this widget, you should see that the connected status is displayed below the rest of your content. Of course, you may want to use a nicer icon/image to represent the connected state.

image

Comments (1) -

  • car broker

    16/11/2010 8:11:23 PM |

    Useful information shared..Iam very happy to read this article..thanks for giving us nice info.Fantastic walk-through. I appreciate this post.

  • home broadband

    9/03/2011 2:41:42 AM |

    Thank you for another great blog. Where else could anyone get that kind of info written in such a perfect way? I have a presentation that I am presently working on, and I have been looking for such information.

  • Mobile broadband

    20/04/2011 12:43:33 AM |

    I haven’t any word to appreciate this post.... i think this is really a great thing about the fashion. Really I am impressed from this post. I am very happy to read this article. Thanks for giving us nice info. Fantastic walk-through. I appreciate this post.

Comments are closed