Sunday, 08 March 2009 18:54
Article Index
Sensor arrow
Co-ordinate maths
Handling the sensor values
Simulation
All Pages

Android supports little magnetic gizmos inside the handset which can tell your app which way up the phone is, how far its tilted, if it's moving and so on. This opens up a whole new world to developers, be it in utility apps, navigation, games or whatever they can dream up. Accessing these readings in code isn't always straightforward, and if you don't have a physical handset available it might seem impossible. Not so - in this tutorial we show how to make use of the values your app can listen to, and go through the process of building the necessary hooks in your code to test it without a real phone.

This app draws a realtime image showing the direction the handset is tilited in as well as the "steepness" of that tilt. Heres whats on the screen when you tilt the phone down towards the top right corner:

## Sensors working overtime

The collective name for the various signals Android can read is "Sensors". This ability is one of the defining characteristics of these next-gen handsets, or "smartphones on steroids", to use a phase recently seen on the net. The classes of readings available are the orientation (which way up the handset is, which way it is tilting etc), how fast it is moving and where it is. These are termed readings for ORIENTATION, the ACCELEROMETER and the MAGNETIC_FIELD. In general terms, you write your app such that at the start you indicate to Android you wish to be notified of these signals throughout its runtime. You then write handlers which interrupt your main program flow with them, so you make use of these new values and then continue running.

Three values are sent from the orientation sensors: Azimuth, Pitch and Roll. If you imagine you are holding the handset flat in your hand and want to describe accurately how you are tilting it, these are the values you could use.

Azimuth is rotation around the z axis, so in this example it stays flat but it spins round the palm of your hand. The amount of spin is given in degrees, 0 being North, turning through 90 East right round back to North again at 360, which is of course 0 as you are back whre you started.

Pitch describes the tilt along the x axis. This is measured from -180 to +180 and the x axis is the line .which runs with your thumb when you hold your hand in front of you with your thumb stretched as far as it can, fingers stright forward.

Roll again has a range from -180 to +180 and describes how far tilted along the y axis the handset is. The y axis runs along your fingers when you hold your hand straight out in front of you. So if you do this and move your hand so the tips of your fingers go up and down (keeping them straight) you are changing the roll.

Our task is to get these readings into our code in a form we can use. The form they will take depends on the purpose, but a common need is just to know if the handset is tilted, and if so how far. Another way of putting that is to say which corner of the handset is the closest to the floor, and by how much. If you have these 2 values, you can write your code entirely in those terms alone.

You may have noticed there was a lot of talk about axis and different parameters when describing the orientation sensor reading just then, and thats because it has been written in mathematical terms to provide them as co-ordinates. The actual term is rectangular co-ordinates, as they define points on a graph relative to a fixed origin and axis. There is an alternative way to describe these point just as precisely. These are termed polar co-ordinates. Instead of saying "go so far along the x axis, then so far along the y axis" as with the rectangular system you say "go a distance of n at an angle of so many degrees". Sound familar? Thats very much like the reading we want. Therefore, we are going to need some form of rectangular to polar co-ordinate conversion.

A look at the app in action:

The app here shows how to use the orientaion sensors in both hardware and software. This is useful because when developing you often want to keep switching between a real handset and the emulator, but of course the emulator doesn't support hardware (normally!), and the handset prefers to use its own hardware so won't listen to the simulator on your PC. This is accomplished using a proxy class in java which is aware of everything except its actual sensor listening method - we'll come to that shortly.

The full source is given at the end of this article, but for now lets look at exactly how these sensor values arrive in hardware:

```
SyntaxHighlighter.defaults["collapse"] = false;
SyntaxHighlighter.defaults["gutter"] = true;
SyntaxHighlighter.defaults["smart-tabs"] = true;
SyntaxHighlighter.defaults["tab-size"] = 4;
SyntaxHighlighter.defaults["toolbar"] = true;
SyntaxHighlighter.defaults["wrap-lines"] = true;
SyntaxHighlighter.all();
public void onSensorChanged(SensorEvent event) {
if (!isRegistered) return;

switch(event.sensor.getType()) {
case Sensor.TYPE_ORIENTATION: {
for (int i = 0; i < 3; i++) {
mActivity.mSensorArrowView.mOrientationValues[i] = event.values[i];
}
mActivity.mSensorArrowView.calcAngle();
mActivity.mSensorArrowView.invalidate();
}
break;
}
}
```

And with the software simulator

```public void onSensorChanged(SensorEvent event) {
if (!isRegistered) return;

switch(event.type) {
case Sensor.TYPE_ORIENTATION: {
for (int i = 0; i < 3; i++) {
mActivity.mSensorArrowView.mOrientationValues[i] = event.values[i];
}
mActivity.mSensorArrowView.calcAngle();
mActivity.mSensorArrowView.invalidate();
}
break;
}
}
```

The values arrive as part of a SensorEvent object, so all we do is extract them appropriately. The trick is, the SensorEvent object isn't the same type for each method - for hardware it's android.hardware.SensorEvent and with the simulator it's org.openintents.sensorsimulator.hardware.SensorEvent.

To handle both cases in the same app, we use these methods in each child class using the proxy as a parent. Here's how that parent looks:

```public abstract class ProxySensor {
protected SensorArrowActivity mActivity;
public boolean isRegistered = false;

public ProxySensor(SensorArrowActivity activity) {
mActivity = activity;
}

public abstract boolean register();
public abstract boolean unregister();
}
```

So we can see any children must implement a register() and unregister() method. If we want them to listen to sensor events, we must also implement the appropriate sensor listening interface. Here's how the hardware one does it:

```import android.app.Activity;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class HardwareSensor extends ProxySensor implements SensorEventListener {
private SensorManager mSensorManager;

public HardwareSensor(SensorArrowActivity activity) {
super(activity);

mSensorManager = (SensorManager) mActivity.getSystemService(
Activity.SENSOR_SERVICE);
isRegistered = register();
}
...
}
```

And the software one:

```import org.openintents.sensorsimulator.hardware.Sensor;
import org.openintents.sensorsimulator.hardware.SensorEvent;
import org.openintents.sensorsimulator.hardware.SensorEventListener;
import org.openintents.sensorsimulator.hardware.SensorManagerSimulator;

import android.app.Activity;
import android.hardware.SensorManager;

public class SoftwareSensor extends ProxySensor implements SensorEventListener {
private SensorManagerSimulator mSensorManager;

public SoftwareSensor(SensorArrowActivity activity) {
super(activity);

mSensorManager = SensorManagerSimulator.getSystemService(
mActivity, Activity.SENSOR_SERVICE);
isRegistered = register();
}
...
}
```

Notice the difference in the imports statements for each class - that's how the SensorEvent mentioned earlier is a different type in each listener method.

The point to all this is we can now switch between hardware and software sensors no matter if the app is running on a handset (hardware) or the emulator (software):

When  onSensorChanged() fires we have values for the Azimuth, Pitch and Roll. We're trying to get the tilt, which is the direction, and the angle, or the steepness, from these. When you think about it, the Azimuth value isn't needed for this as you can in fact treat the pitch and roll values as the x and y co-ordinates of the point in a rectangular co-ordinate system.

In the above diagram point P can be expressed as (x, y) when using rectangular co-ordinates, or (R, t) using polar ones. This is where the java Math package comes to the rescue. A quick read of the api shows us there are exactly the functions we need for this, using the orientation values we just read. From the calcAngle() method:

```mAngle = (int) ( Math.toDegrees(Math.atan( mOrientationValues[2] / mOrientationValues[1] )));
mTilt = (int) Math.sqrt(( Math.pow(mOrientationValues[2], 2))
+ (Math.pow(mOrientationValues[1], 2)));
```

Some more adjustments must be made to the angle according to the quadrant the point is positioned in, but after that we're done. At any time in our app, the mAngle value will show which way the handset is tilted, and the mTilt value how steep the tilt is.

In this application we will make use of these just by showing a radar type display with an arrow at the centre. The arrow will point in the tilts direction, and its length will show the steepness. So if the handset is perfectly flat there should be no arrow.

The applications onDraw() method draws the background, then the circle, centre and markers and calls drawArrow() to show the arrow:

```private void drawArrow(Canvas canvas, Paint paint) {
if (mTilt == 0) return;

int length = ((mRadius - 30) * mTilt) / 100;

if (length < 0) length = 0;

Path mPath = new Path();
mPath.moveTo(mCenterX, mCenterY);
mPath.rLineTo(10, 0);
mPath.rLineTo(0, -length);
mPath.rLineTo(10, 0);
mPath.rLineTo(-20, -30);
mPath.rLineTo(-20, 30);
mPath.rLineTo(10, 0);
mPath.rLineTo(0, length);
mPath.rLineTo(10, 0);
mPaint.setStyle(Paint.Style.FILL);
canvas.rotate(mAngle, mCenterX, mCenterY);
canvas.drawPath(mPath, mPaint);
canvas.rotate(-mAngle, mCenterX, mCenterY);
}
```

We're using Androids Path mechanism here to construct an outline in memory, then call drawPath() on it to get it to appear immediately. We are also rotating the whole canvas by the amount in mAngle, just for the duration of this draw, so it appears the arrow itself has been rotated correctly.

This application has a few more things to draw. The current values for the raw readings and the computed angle and tilt are shown and cycled through if you tap the screen. These are handled by appropriate functions which should be clear from the code.

## Sensors without hardware

How can you test the sensors if you have no hardware? Fortunately OpenIntents offer their SensorSimulator for precisely this purpose, available here. It runs as a separate Windows/Linux application, alongside your emulator, and lets you rotate a representaion of the handset around with your mouse: the sensor values this generates are sent to the emulator just as if it was a real handset.

For completeness, heres's how to set up the sensor simulator, but it's detailed more fully here.

2. Unzip it and install to your emulator the SensorSimulatorSettings apk file from the /bin folder
3. On your PC run the Sensor Simulator jar by double clicking it
4. Back in the emulator match up the IP address shown in the SensorSimulator settings window with the one in the app on the emulator. On the emulator, you can check there is a connection using the "Testing" tab but be sure to disable this when you run your own app.
5. Finally run an app on the emulator which has been written to use the PC simulator, such as the one presented here ;-)

Joomla Templates and Joomla Extensions by JoomlaVision.Com