≡ Menu

Smart Grids, Part 10: The Android Application, rounded out

We started out a few chapters ago by creating a project with Android tooling, including the UI. Then we took a slight detour, describing an API by which our application – a producer, in the messaging paradigm – will be able to send data to a consumer.

So now that we have the front end described and the final destination built, it’s time to actually connect the UI to the transport such that we’re generating valid light level data, and sending it.

One thing is very relevant in this code: it’s built for Android 2.3, Gingerbread — not Android 4.0 or 4.1. That doesn’t mean it won’t run properly on more recent versions of Android, but this does affect how some of the code works and how certain permissions are requested. I’ll try to make a note of where these changes affect our application as we go.

So we have our UI as built in the previous Android article. The layout is described in main.xml; here’s a sample of some of it.

<ToggleButton
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            style="@android:style/Widget.Button.Toggle"
            android:textOn="@string/sensorEnabled"
            android:textOff="@string/sensorDisabled"
            android:id="@+id/sensorActive"/>

This is a ToggleButton, of course; it’s the button that indicates whether the application is supposed to be sending data to the consumer or not. This layout is active; even without any code, the button works. But a UI with no actual activity behind it isn’t worth much. So let’s look at how we get this thing to do something custom.

The initialization of an Android Activity is through onCreate(). The first thing onCreate() should do, after calling the superclass’ onCreate(), is initialize the UI with the view layer, using an autogenerated reference class, R.

@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.main);
   ...

Our reference there is R.layout.main. R is built from our res directory; in this case, layout is where the layouts are, and our layout is named main.xml; thus, R.layout.main.

R.java (which is autogenerated and not part of the source code) looks like this with the current layout:

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.redhat.osas.sensor;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int deviceId=0x7f050002;
        public static final int lightLevel=0x7f050003;
        public static final int logBox=0x7f050004;
        public static final int routerUrl=0x7f050001;
        public static final int sensorActive=0x7f050000;
    }
    public static final class layout {
        public static final int main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040000;
        public static final int deviceId=0x7f040008;
        public static final int deviceIdLabel=0x7f040007;
        public static final int lightLevelLabel=0x7f040003;
        public static final int logLabel=0x7f040004;
        public static final int routerUrl=0x7f040002;
        public static final int routerUrlLabel=0x7f040001;
        public static final int sensorDisabled=0x7f040006;
        public static final int sensorEnabled=0x7f040005;
    }
}

Once you get past the mechanics of declaring the UI, using it’s actually pretty easy. You’ve seen how to set the content to a specific view; getting a reference to a widget is just a matter of calling findviewById(), so getting a reference to sensorActive – a ToggleButton – involves the following construct:

ToggleButton sensorActive = (ToggleButton) findViewById(R.id.sensorActive);

At this point, actually programming the GUI is just a matter of providing event listeners, which is not only documented by the Android developer guides, but follows the Swing mechanisms pretty closely, too.

There are still some things we need that we haven’t provided: namely, location and identification services. We’ll need those before we can start actually doing things with our UI.

We acquire them via getSystemService().

To get the voice mail id, first we get the Telephony Service:

telephonyManager=(TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);

To get the voice mail id itself, we query the TelephonyManager:

String voiceMailId = telephonyManager.getVoiceMailNumber();

Our other services are aquired in much the same way:

// provides access to GPS
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
// provides access to the sensors...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
// gets the actual primary light sensor
lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

Now things get a little wonky, mostly because this is a mobile device, with multiple states. Basically, we want to send data if:

  • The application is in active, or unpaused, state.
  • We have a valid destination.
  • We have GPS data.
  • The user has told the application to send data.

Most of the UI and event code in the application (LightSensorActivity.java) is geared around enforcing those requirements.

Two methods address the status of the application, onPause() and onResume().

addCheckedChangeListener() handles the event generated when the user indicates that they want to submit data; when the user wants to send data, location and sensor updates are requested.

The actual sending of data takes place when light sensor updates are detected. Barring exception handling, the code looks a lot like this:

if (!connector.isConnected()) {
    connector.connect(routerUrl.getText().toString());
}
DataPoint dataPoint = new DataPoint(voiceMailId,
    latitude, longitude,
    lightLevel.getProgress(), maxLevel);

String data = mapper.writeValueAsString(dataPoint);
connector.publish(data);

Note how we’re transport-neutral; we have a URL used as an end point, and we send the data as a JSON string to that endpoint every time the light level changes. We also update the UI so the user can tell what’s being sent.

That last code snippet is actually the crucial part of the entire application. Everything else – all the fairly complicated UI code (complicated considering the outcome) is designed to feed that code with valid data.

This is where the Android 2.3 compatibility factors in, by the way. In Android 4.x, this will fail with a threading error, because Android 4.x will not allow you to execute network code like this in the main event thread.

This is a good thing, but a painful thing nonetheless. The proper way to handle this is to create a java Executor, and submit the publication request in a Runnable, executed by the Executor thread. That’s easy to do, and will definitely be part of the future of the application, but I didn’t discover it until I helped test an AMQP library on Android.


So what now? We’ll look at the application actually running, and then we’ll add high availability. In order to do that, we’re going to move away from a REST endpoint provided by JBoss, and over to a set of AMQP brokers, provided by the Fedora Messaging Infrastructure.