Introducing Accelerometers

Accelerometers, as their name suggests, are used to measure acceleration. They are also sometimes referred to as gravity sensors.

Accelerometers are also known as gravity sensors because of their inability to differentiate betiveen acceleration caused by movement and gravity. As a result, an accelerometer detecting acceleration on the z-axis (up/down) will read -9.8m/s1 when it's at rest (this value is available as the SensorManager.STANDARD_GRAVITY constant).

Acceleration is defined as the rate of change of velocity, so accelerometers measure how quickly the speed of the device is changing in a given direction. Using an accelerometer you can detect movement and, more usefully, the rate of change of the speed of that movement.

It's important to note that accelerometers do not measure velocity, so you can't measure speed directly based on a single accelerometer reading. Instead you need to measure changes in acceleration over time.

Generally you'll be interested in acceleration changes relative to a rest state, or rapid movement (signified by rapid changes in acceleration) such as gestures used for user input. In the former case you'll often need to calibrate the device to calculate the initial orientation and acceleration to take those effects into account for future results.

Detecting Acceleration Changes

Acceleration can be measured along three directional axes: left-right (lateral), forward-backward (longitudinal), and up-down (vertical). The Sensor Manager reports accelerometer Sensor changes along all three axes.

The values passed in through the values property of the Sensor Event Listener's Sensor Event parameter represent lateral, longitudinal, and vertical acceleration, in that order.

Figure 14-1 illustrates the mapping of the three directional acceleration axes in relation to the device at rest. The Sensor Manager considers the device "at rest'' when it is sitting face up on a flat surface in portrait orientation.

vertical

longitudinal

vertical

longitudinal

lateral

FIGURE 14-1

lateral

FIGURE 14-1

> x-axis (lateral) Sideways (left or right) acceleration, for which positive values represent movement toward the right side of the device, and negative values indicate movement to the left. For example, positive x-axis acceleration would be detected on a device flat on its back, facing up, and in portrait orientation being moved along a surface to your right.

> y-axis (longitudinal) Forward or backward acceleration, for which forward acceleration is represented by a positive value. In the same configuration as described for lateral movement, you would create positive longitudinal acceleration by moving the device in the direction of the top of the device.

> z-axis (vertical) Upward or downward acceleration, for which positive represents upward movement such as the device being lifted. While at rest the vertical accelerometer will register -9.8m/s2 as a result of gravity.

As described earlier, you monitor changes in acceleration using a Sensor Event Listener. Register an implementation of SensorEventListener with the Sensor Manager, using a Sensor object of type Sensor.TYPE_ACCELEROMETER to request accelerometer updates. Listing 14-2 registers the default accelerometer using the normal update rate.

LISTING 14-2: Listening to changes to the default accelerometer Available for downloadon SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE); Wrox.com int sensorType = Sensor.TYPE_ACCELEROMETER;

sm.registerListener(mySensorEventListener, sm.getDefaultSensor(sensorType), SensorManager.SENSOR_DELAY_NORMAL);

Your Sensor Listener should implement the onSensorChanged method that will be fired when acceleration in any direction is measured.

The onSensorChanged method receives a SensorEvent that includes a float array containing the acceleration measured along all three axes. Based on a rest state of the device sitting flat on its back in portrait orientation, the first element represents lateral, the second longitudinal, and the final vertical acceleration, as shown in the following extension to Listing 14-2.

final SensorEventListener mySensorEventListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) {

if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { float xAxis_lateralA = sensorEvent.values[0]; float yAxis_longitudinalA = sensorEvent.values[1]; float zAxis_verticalA = sensorEvent.values[2]; // TODO apply the acceleration changes to your application.

Creating a G-Forceometer

You can create a simple tool to measure g-force by summing the acceleration in all three directions and comparing it to the value in free fall. In the following example you'll create a simple device to measure g-force using the accelerometers to determine the current force being exerted on the device.

Thanks to gravity the force exerted on the device at rest is 9.8m/s2 toward the center of the Earth. In this example you'll negate the force of gravity by accounting for it using the SensorManager.STANDARD_GRAVITY constant.

1. Start by creating a new Forceometer project with a Forceometer Activity. Modify the main.xml layout resource to display two centered lines of large, bold text that will be used to display the current g-force and maximum observed g-force:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/acceleration" android:gravity="center" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textSize="32sp" android:text="CENTER" android:editable="false" android:singleLine="true" android:layout_margin="10px"/>

<TextView android:id="@+id/maxAcceleration" android:gravity="center" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textSize="40sp" android:text="CENTER" android:editable="false" android:singleLine="true" android:layout_margin="10px"/>

</LinearLayout>

2. Within the Forceometer Activity, create instance variables to store references to both TextView Views and the SensorManager. Also create variables to record the last and maximum detected acceleration values:

SensorManager sensorManager; TextView accelerationTextView; TextView maxAccelerationTextView; float currentAcceleration = 0; float maxAcceleration = 0;

3. Create a new SensorEventListener implementation that sums the acceleration detected along each axis and negates the acceleration caused by gravity. It should update the current and maximum acceleration whenever a change in acceleration is detected:

private final SensorEventListener sensorEventListener = new SensorEventListener() { double calibration = SensorManager.STANDARD_GRAVITY;

public void onAccuracyChanged(Sensor sensor, int accuracy) { }

public void onSensorChanged(SensorEvent event) { double x = event.values[0]; double y = event.values[1]; double z = event.values[2];

Math.pow(z, 2))); currentAcceleration = Math.abs((float)(a-calibration));

if (currentAcceleration > maxAcceleration) maxAcceleration = currentAcceleration;

4. Update the onCreate method to register your new Listener for accelerometer updates using the SensorManager. Take the opportunity to get a reference to the two Text Views:

©Override public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.main);

accelerationTextView = (TextView)findViewByld(R.id.acceleration); maxAccelerationTextView = (TextView)findViewByld(R.id.maxAcceleration); sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(sensorEventListener, accelerometer,

SensorManager.SENSOR_DELAY_FASTEST);

5. The accelerometers can be very sensitive, so updating the Text Views for every detected acceleration change can be very expensive. Instead, create a new updateGUi method that synchronizes with the GUI thread based on a Timer before updating the Text Views:

private void updateGUI() {

runOnUiThread(new Runnable() { public void run() {

String currentG = currentAcceleration/SensorManager.STANDARD_GRAVITY + "Gs";

accelerationTextView.setText(currentG); accelerationTextView.invalidate();

String maxG = maxAcceleration/SensorManager.STANDARD_GRAVITY + "Gs";

maxAccelerationTextView.setText(maxG);

maxAccelerationTextView.invalidate();

6. Finally, update the onCreate method to start a timer that's used to update the GUI every 100ms:

©Override public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.main);

accelerationTextView = (TextView)findViewByld(R.id.acceleration); maxAccelerationTextView = (TextView)findViewByld(R.id.maxAcceleration); sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);

Sensor accelerometer =

sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(sensorEventListener, accelerometer,

SensorManager.SENSOR_DELAY_FASTEST);

Timer updateTimer = new Timer("gForceUpdate"); updateTimer.scheduleAtFixedRate(new TimerTask() { public void run() { updateGUI();

All code snippets in this example are part of the Chapter 14 G-Forceometer project, available for download at Wrox.com.

Once you're finished you'll want to test this out. Ideally you can do that in an F16 while Maverick performs high-g maneuvers over the Atlantic. That's been known to end badly, so failing that you can experiment with running or driving in the safety of your neighborhood.

Given that keeping constant watch on your handset while driving, cycling, or flying is also likely to end poorly, you might consider some further enhancements before you take it out for a spin.

Consider incorporating vibration or media player functionality to shake or beep with an intensity proportional to your current force, or simply log changes as they happen for later review.

Determining Your Orientation

The orientation Sensor is a combination of the magnetic field Sensors, which function as an electronic compass, and accelerometers, which determine the pitch and roll.

If you've done a bit of trigonometry you've got the skills required to calculate the device orientation based on the accelerometer and magnetic field values along all three axes. If you enjoyed trig as much as I did you'll be happy to learn that Android does these calculations for you.

In fact, Android provides two alternatives for determining the device orientation. You can query the orientation Sensor directly or derive the orientation using the accelerometers and magnetic field Sensors. The latter option is slower, but offers the advantages of increased accuracy and the ability to modify the reference frame when determining your orientation. The following sections demonstrate both techniques.

Using the standard reference frame, the device orientation is reported along three dimensions, as illustrated in Figure 14-2. As when using the accelerometers, the device is considered at rest faceup on a flat surface.

> x-axis (azimuth) The azimuth (also heading or yaw) is the direction the device is facing around the x-axis, where 0/360 degrees is north, 90 east, 180 south, and 270 west.

heading

roll

heading

roll

Android Azimuth Pitch Roll

U pitch

FIGURE 14-2

U pitch

FIGURE 14-2

> y-axis (pitch) Pitch represents the angle of the device around the y-axis. The tilt angle returned shows 0 when the device is flat on its back, -90 when it is standing upright (top of device pointing at the ceiling), 90 when it's upside down, and 180/-180 when it's facedown.

> z-axis (roll) The roll represents the device's sideways tilt between -90 and 90 degrees on the z-axis. Zero is the device flat on its back, -90 is the screen facing left, and 90 is the screen facing right.

Determining Orientation Using the Orientation Sensor

The simplest way to monitor device orientation is by using a dedicated orientation Sensor. Create and register a Sensor Event Listener with the Sensor Manager, using the default orientation Sensor, as shown in Listing 14-3.

LISTING 14-3: Determining orientation using the orientation Sensor

SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE); int sensorType = Sensor.TYPE_ORIENTATION; sm.registerListener(myOrientationListener, sm.getDefaultSensor(sensorType), SensorManager.SENSOR_DELAY_NORMAL);

When the device orientation changes, the onSensorChanged method in your SensorEventListener implementation is fired. The SensorEvent parameter includes a values float array that provides the device's orientation along three axes.

The first element of the values array is the azimuth (heading), the second pitch, and the third roll.

final SensorEventListener myOrientationListener = new SensorEventListener() { public void onSensorChanged(SensorEvent sensorEvent) {

if (sensorEvent.sensor.getType() == Sensor.TYPE_ORIENTATION) { float headingAngle = sensorEvent.values[0]; float pitchAngle = sensorEvent.values[1]; float rollAngle = sensorEvent.values[2];

// TODO Apply the orientation changes to your application.

public void onAccuracyChanged(Sensor sensor, int accuracy) {}

Calculating Orientation Using the Accelerometer and Magnetic Field Sensors

The best approach for finding the device orientation is to calculate it from the accelerometer and magnetic field Sensor results directly.

This technique enables you to change the orientation reference frame to remap the x-, y-, and z-axes to suit the device orientation you expect during use.

This approach uses both the accelerometer and magnetic field Sensors, so you need to create and register two Sensor Event Listeners. Within the onSensorChanged methods for each Sensor Event Listener, record the values array property received in two separate field variables, as shown in Listing 14-4.

Available for download on Wrox.com

LISTING 14-4: Finding orientation using the accelerometer and magnetic field Sensors

Available for download on float[] accelerometerValues, Wrox.com float[] magneticFieldValues, final SensorEventListener myAccelerometerListener = new SensorEventListener() public void onSensorChanged(SensorEvent sensorEvent) i if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) accelerometerValues = sensorEvent.values,

public void onAccuracyChanged(Sensor sensor, int accuracy) {}

final SensorEventListener myMagneticFieldListener = new SensorEventListener() i public void onSensorChanged(SensorEvent sensorEvent) i if (sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) magneticFieldValues = sensorEvent.values,

public void onAccuracyChanged(Sensor sensor, int accuracy) {}

Register both with the Sensor Manager, as shown in the following code extending Listing 14-4; this snippet uses the default hardware and UI update rate for both Sensors:

SensorManager sm = (SensorManager)getSystemService(Context.SENSOR_SERVICE); Sensor aSensor = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); Sensor mfSensor = sm.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

sm.registerListener(myAccelerometerListener, aSensor,

SensorManager.SENSOR_DELAY_UI);

sm.registerListener(myMagneticFieldListener, mfSensor,

SensorManager.SENSOR_DELAY_UI);

To calculate the current orientation from these Sensor values you use the getRotationMatrix and getOrientation methods from the Sensor Manager, as follows. Note that getOrientation returns radians rather than degrees.

float[] values = new float[3]; float[] R = new float[9]; SensorManager.getRotationMatrix(R, null, accelerometerValues, magneticFieldValues); SensorManager.getOrientation(R, values);

// Convert from radians to degrees. values[0] = (float) Math.toDegrees(values[0]) values[1] = (float) Math.toDegrees(values[1]) values[2] = (float) Math.toDegrees(values[2])

Remapping the Orientation Reference Frame

To measure device orientation using a reference frame other than the default described earlier, use the remapCoordinateSystem method from the Sensor Manager.

Earlier in this chapter the standard reference frame was described as the device being faceup on a flat surface. This method lets you remap the coordinate system used to calculate your orientation, for example by specifying the device to be at rest when mounted vertically.

Available for download on Wrax.com

The remapCoordinateSystem method accepts four parameters:

> The initial rotation matrix, found using getRotationMatrix, as described earlier

> A variable used to store the output (transformed) rotation matrix

> The remapped x-axis

> The remapped y-axis

Two final parameters are used to specify the new reference frame. The values used specify the new x- and y-axes relative to the default frame. The Sensor Manager provides a set of constants to let you specify the axis values: axis_x, axis_y, axis_z, axis_minus_x, axis_minus_y, and

AXIS_MINUS_Z.

Listing 14-5 shows how to remap the reference frame so that a device is at rest when mounted vertically — held in portrait mode with its screen facing the user — as shown in Figure 14-3.

LISTING 14-5: Remapping the orientation reference frame

heading

o pitch

roll

FIGURE 14-3

Available for download on Wrax.com

SensorManager.getRotationMatrix(R, null, aValues, mValues);

float[] outR = new float[9]; SensorManager.remapCoordinateSystem(R,

SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);

SensorManager.getOrientation(outR, values);

// Convert from radians to degrees. values[0] = (float) Math.toDegrees(values[0]) values[1] = (float) Math.toDegrees(values[1]) values[2] = (float) Math.toDegrees(values[2])

Creating a Compass and Artificial Horizon

In Chapter 4 you created a simple CompassView to experiment with owner-drawn controls. In this example you'll extend the functionality of the Compass View to display the device pitch and roll, before using it to display the device orientation.

1. Open the Compass project you created in Chapter 4. You will be making changes to the CompassView as well as the Compass Activity used to display it. To ensure that the view and controller remain as decoupled as possible, the CompassView won't be linked to the Sensors directly; instead it will be updated by the Activity. Start by adding field variables and get/set methods for pitch and roll to the CompassView.

public float getPitch() { return pitch;

public void setPitch(float pitch) { this.pitch = pitch;

public float getRoll() { return roll;

public void setRoll(float roll) { this.roll = roll;

2. Update the onDraw method to include two circles that will be used to indicate the pitch and roll values.

@Override protected void onDraw(Canvas canvas) { [ ... Existing onDraw method ... ]

2.1. Create a new circle that's half filled and rotates in line with the sideways tilt (roll).

RectF rollOval = new RectF((mMeasuredWidth/3)-mMeasuredWidth/7,

(mMeasuredHeight/2)-mMeasuredWidth/7, (mMeasuredWidth/3)+mMeasuredWidth/7,

(mMeasuredHeight/2)+mMeasuredWidth/7 );

markerPaint.setStyle(Paint.Style.STROKE); canvas.drawOval(rollOval, markerPaint); markerPaint.setStyle(Paint.Style.FILL); canvas.save();

canvas.rotate(roll, mMeasuredWidth/3, mMeasuredHeight/2); canvas.drawArc(rollOval, 0, 180, false, markerPaint);

canvas.restore();

2.2. Create a new circle that starts half filled and varies between full and empty based on the forward angle (pitch):

RectF pitchOval = new RectF((2*mMeasuredWidth/3)-mMeasuredWidth/7,

(mMeasuredHeight/2)-mMeasuredWidth/7, (2*mMeasuredWidth/3)+mMeasuredWidth/7,

(mMeasuredHeight/2)+mMeasuredWidth/7 );

markerPaint.setStyle(Paint.Style.STROKE); canvas.drawOval(pitchOval, markerPaint); markerPaint.setStyle(Paint.Style.FILL); canvas.drawArc(pitchOval, 0-pitch/2, 180+(pitch) markerPaint.setStyle(Paint.Style.STROKE);

false, markerPaint);

That completes the changes to the CompassView. If you run the application now it should appear as shown in Figure 14-4.

Now update the Compass Activity. Use the Sensor Manager to listen for orientation changes using the magnetic field and accelerometer Sensors. Start by adding local field variables to store the last magnetic field and accelerometer values, as well as references to the CompassView and SensorManager.

float[] aValues = new float[3]; float[] mValues = new float[3]; CompassView compassView; SensorManager sensorManager;

Create a new updateOrientation method that uses new heading, pitch, and roll values to update the CompassView.

false, markerPaint);

Onsensorchanged Pitch Roll
FIGURE 14-4

private void updateOrientation(float[] values) { if (compassView!= null) {

compassView.setBearing(values[0]); compassView.setPitch(values[1]); compassView.setRoll(-values[2]); compassView.invalidate();

Update the onCreate method to get references to the CompassView and SensorManager, and initialize the heading, pitch, and roll.

©Override public void onCreate(Bundle savedlnstanceState) { super.onCreate(savedlnstanceState); setContentView(R.layout.main);

compassView = (CompassView)this.findViewByld(R.id.compassView); sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE); updateOrientation(new float[] {0, 0, 0});

7. Create a new calculateOrientation method to evaluate the device orientation using the last recorded accelerometer and magnetic field values.

private float[] calculateOrientation() { float[] values = new float[3]; float[] R = new float[9];

SensorManager.getRotationMatrix(R, null, aValues, mValues); SensorManager.getOrientation(R, values);

// Convert from Radians to Degrees. values[0] = (float) Math.toDegrees(values[0]); values[1] = (float) Math.toDegrees(values[1]); values[2] = (float) Math.toDegrees(values[2]);

return values;

8. Implement a SensorEventListener as a field variable. Within onSensorChanged it should check for the calling Sensor's type and update the last accelerometer or magnetic field values as appropriate before making a call to updateOrientation using the calculateOrientation method.

private final SensorEventListener sensorEventListener = new SensorEventListener() {

public void onSensorChanged(SensorEvent event) {

if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)

aValues = event.values; if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) mValues = event.values;

updateOrientation(calculateOrientation());

public void onAccuracyChanged(Sensor sensor, int accuracy) {}

9. Now override onResume and onStop to register and unregister the SensorEventListener when the Activity becomes visible and hidden, respectively.

©Override protected void onResume() {

super.onResume();

Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); Sensor magField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

sensorManager.registerListener(sensorEventListener, accelerometer,

SensorManager.SENSOR_DELAY_FASTEST); sensorManager.registerListener(sensorEventListener, magField,

SensorManager.SENSOR_DELAY_FASTEST);

©Override protected void onStop() {

sensorManager.unregisterListener(sensorEventListener); super.onStop();

If you run the application now you should see the three face dials update dynamically when the orientation of the device changes.

10. An artificial horizon is more useful if it's mounted vertically. Modify the reference frame of the artificial horizon to match this orientation by updating calculateOrientation to remap the coordinate system.

private float[] calculateOrientation() { float[] values = new float[3]; float[] R = new float[9]; float[] outR = new float[9];

SensorManager.getRotationMatrix(R, null, aValues, mValues); SensorManager.remapCoordinateSystem(R,

SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);

SensorManager.getOrientation(outR, values);

// Convert from Radians to Degrees. values[0] = (float) Math.toDegrees(values[0]); values[1] = (float) Math.toDegrees(values[1]); values[2] = (float) Math.toDegrees(values[2]);

return values;

All code snippets in this example are part of the Chapter 14 Artificial Horizon project, available for download at Wrox.com.

CONTROLLING DEVICE VIBRATION

In Chapter 9 you learned how to create Notifications that can use vibration to enrich event feedback. In some circumstances you may want to vibrate the device independently of Notifications. Vibrating the device is an excellent way to provide haptic user feedback, and is particularly popular as a feedback mechanism for games.

To control device vibration, your applications needs the vibrate permission. Add this to your application manifest using the following XML snippet:

<uses-permission android:name="android.permission.VIBRATE"/>

Device vibration is controlled through the Vibrator Service, accessible via the getSystemService method, as shown in Listing 14-6.

LISTING 14-6: Controlling device vibration Available for download on String vibratorService = Context.VIBRATOR_SERVICE; Wrox.com Vibrator vibrator = (Vibrator)getSystemService(vibratorService);

Call vibrate to start device vibration; you can pass in either a vibration duration or a pattern of alternating vibration/pause sequences along with an optional index parameter that will repeat the pattern starting at the index specified. Both techniques are demonstrated in the following extension to Listing 14-6:

long[] pattern = {1000, 2000, 4000, 8000, 16000 }; vibrator.vibrate(pattern, 0); // Execute vibration pattern. vibrator.vibrate(1000); // Vibrate for 1 second.

To cancel vibration call cancel; exiting your application will automatically cancel any vibration it has initiated.

SUMMARY

In this chapter you learned how to use the Sensor Manager to let your application respond to the physical environment. You were introduced to the Sensors available on the Android platform and learned how to listen for Sensor Events using the Sensor Event Listener and how to interpret those results.

Then you took a more detailed look at the accelerometer, orientation, and magnetic field detection hardware, using these Sensors to determine the device's orientation and acceleration. In the process you created a g-forceometer and an artificial horizon.

You also learned:

> Which Sensors are available to Android applications

> How to remap the reference frame when determining a device's orientation

> The composition and meaning of the Sensor Event values returned by each sensor

> How to use device vibration to provide physical feedback for application events

In the final chapter, you'll be introduced to some of the advanced Android features. You'll learn more about security, how to use AIDL to facilitate interprocess communication, and using Wake Locks. You'll be introduced to Android's TTS library and learn about Android's User Interface and graphics capabilities by exploring animations and advanced Canvas drawing techniques. Finally, you'll be introduced to the SurfaceView and touch-screen input functionality.

Mobile Apps Made Easy

Mobile Apps Made Easy

Quick start guide to skyrocket your offline and online business success with mobile apps. If you know anything about mobile devices, you’ve probably heard that famous phrase coined by one of the mobile device’s most prolific creators proclaiming that there’s an app for pretty much everything.

Get My Free Training Guide


Responses

  • ARTEMIA
    How would a veriical accelerometer be at rest?
    7 years ago

Post a comment