"A human being should be able to change a diaper, plan an invasion, butcher a hog, conn a ship, design a building, write a sonnet, balance accounts, build a wall, set a bone, comfort the dying, take orders, give orders, cooperate, act alone, solve equations, analyze a new problem, pitch manure, program a computer, cook a tasty meal, fight efficiently, die gallantly. Specialization is for insects." (Robert A. Heinlein)

Tuesday, 13 November 2012

Android programming : Exploring sensors

Sensors are one of the the things that make mobile development different, and interesting, from the programming of our desktop computers. Modern mobile devices with their combined capabilities of communicating, imaging and sensing surrounding environment looks more like a pocket version of a artificial satellite than a desktop computer. This is why, after initial hello-worlding, the first thing I've been looking for in the 'net has been how to read sensors in an Android application. I got several examples like here, all showing how to read a single sensor. I decided so to make more interesting these basic examples in order to build a simple user interface able to discover and read available sensors on a device.

The user interface design

After reading about sensors on Android worked I refined my idea on how the user interface: a selector (also known as spinner) on the top filled with available sensors list, some details on the selected sensor just under the selector and sensors values updating on the bottom.
User interfaces (activities) layout is defined, in Android programming, trough a XML file. Eclipse Android development plug-in provides a handy graphical user interface to arrange activity layout. It worked for me well enough even if I went to manual XML editing a couple of times just to make things a little faster. Eclipse plug-in also provide a lot of useful warnings, to a beginner like me, like reminding not to place hard-coded strings in your interface. By the way here is, at last, my interface definition.

 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="1dp"
android:gravity="top" >
<Spinner
android:id="@+id/sensorsList"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/nameLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/sensorsList"
android:text="@string/name" />
<TextView
android:id="@+id/vendorLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/nameLabel"
android:text="@string/vendor" />
<TextView
android:id="@+id/typeLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/vendorLabel"
android:text="@string/type" />
<TextView
android:id="@+id/nameValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/vendorLabel"
android:layout_alignParentRight="true"
android:text="------" />
<TextView
android:id="@+id/typeValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@+id/vendorLabel"
android:text="------" />
<TextView
android:id="@+id/vendorValue"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="@+id/typeValue"
android:layout_alignParentRight="true"
android:text="------" />
<GridView
android:id="@+id/sensorValues"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/typeLabel"
android:layout_marginTop="27dp"
android:numColumns="1" >
</GridView>
</RelativeLayout>

Making it live …

My sensor exploring application is going to do a handful of basic operations:
  • Initialize properly and get the list of available sensors
  • React to spinner selection and prepare to listen data from selected sensor
  • Listen sensor data changes and display them
  • Close nicely when stopped or paused
Android sensors are all handled by the SensorManager class a event listener can be attached to it in order to run code on sensor changes. So my activity will implement the SensorEventListener interface and attach itself to sensor manager. On a similar way the same activity class will implement the OnItemSelectedListener class that, attached to the spinner object, will allow my code to react to the control selection changes.
public class SensorsActivity extends Activity implements SensorEventListener, OnItemSelectedListener
{
...
}

I know that a class doing all it's not the better design, it's a well known anti-pattern to be honest, but this is a demo code and I'm not going to maintain it.
I made some private fields for SensorManager, some UI elements and a list of discovered sensors:
...
// Sensors manager
private SensorManager sm = null;
// Some UI elements
private Spinner spin = null;
private TextView name = null;
private TextView vendor = null;
private TextView type = null;
private GridView results = null;
// List of discovered sensors
private List<Sensor> sensorsList;
private int selected = -1;
...
at the activity creation the private fields are initialized and the spinner is filled with the list of available sensors
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sensors);
// get sensors manager
sm = (SensorManager) getSystemService(SENSOR_SERVICE);
// get UI elements handlers
spin = (Spinner) findViewById(R.id.sensorsList);
name = (TextView) findViewById(R.id.nameValue);
type = (TextView) findViewById(R.id.typeValue);
vendor = (TextView) findViewById(R.id.vendorValue);
results = (GridView) findViewById(R.id.sensorValues);
// set spinner listener
spin.setOnItemSelectedListener(this);
// Fills the spinner
sensorsList = sm.getSensorList(Sensor.TYPE_ALL);
ArrayList<String> als = new ArrayList<String>();
for(Sensor s : sensorsList)
{
als.add(s.getType() + " : " + s.getName());
}
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, als);
spin.setAdapter(adapter);
}
when an item on the spinner is selected the class is registered to read changes from the matching sensor.
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id)
{
// Unregister current
sm.unregisterListener(this);
// Register selected one
Sensor sensor = sensorsList.get(pos);
selected = pos;
sm.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
// write sensors details
name.setText(sensor.getName());
vendor.setText(sensor.getVendor());
type.setText(String.valueOf(sensor.getType()));
}
when the application is suspended, or paused, the listener is unregistered in order to prevent the application from checking sensors, and consuming battery, if not visible.
protected void onStop()
{
sm.unregisterListener(this);
super.onStop();
}
...
protected void onPause()
{
super.onPause();
sm.unregisterListener(this);
}
Last but not least when sensor values change the grid view control is updated
public void onSensorChanged(SensorEvent event)
{
String []vals = new String[event.values.length];
for(int j=0;j<vals.length;j++)
{
vals[j] = j + " : " + event.values[j];
}
// Writes sensor data to grid view
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, vals);
results.setAdapter(adapter);
}
and here is how the application looks like ...

Conclusions

I'm new to Android programming but the concepts that lie behind it are far from being new. Many frameworks use XML to define the GUI layout and anybody had a minimal experience with Java Swing knows the use of listeners to handle events. So programming Android has not been to me a so new experience. The real challenge in Android (or generally speaking mobile) development if the application design phase when you have to keep in mind the different way people interact with mobile devices and the many different device your application could run on.

2 comments :