Logo Icon Logo
A Crowd-sourced Cookbook on Writing Great Android® Apps
GitHub logo Twitter logo OReilly Book Cover Art

Using the Accelerometer to Detect Shaking of the Device

Author: Thomas Manthey -- Published? true -- FormatLanguage: W

Problem:

Sometimes it makes sense to evaluate not only on-screen input, but also gestures like tilting or shaking the telephone. But how can you use the accelerometer to detect whether the phone has been shaken?

Solution:

The solution is to register with the accelerometer and to compare the current acceleration values on all three axes to the previous ones. If the values have repeatedly changed on at least two axises and those changes exceed a high enough threshold, you can clearly determine shaking.

Discussion:

Let us first define shaking as a fairly rapid movement of the device in one direction followed by further one in another direction, mostly but not necessarily the opposite. If we want to detect such a shake motion in an activity, we need a connection to the hardware sensors, those are exposed by the class SensorManager. Furthermore we need to define a SensorEventListener and register it with the SensorManager.

So the source of our activity starts like this:

public class ShakeActivity extends Activity {
    /* The connection to the hardware */
    private SensorManager mySensorManager;

    /* The SensorEventListener lets us wire up to the real hardware events */
    private final SensorEventListener mySensorEventListener = new SensorEventListener() {

        public void onSensorChanged(SensorEvent se) {
	     /* we will fill this one later */
        }

	public void onAccuracyChanged(Sensor sensor, int accuracy) {
	    /* can be ignored in this example */
	       }
    };

    ....

To implement SensorEventListener, we have to implement to methods - onSensorChanged(SensorEvent se) and onAccuracyChanged(Sensor sensor, int accuracy). The first one gets called whenever new sensor data is available, the second one whenever the accuracy of measurement changes, e.g. when the location service switches from GPS to network-based. In our example we just need to cover onSensorChanged.

Before we continue, let us define some more variables, which will store the information about values of acceleration and some state.

        /* Here we store the current values of acceleration, one for each axis */
	private float xAccel;
	private float yAccel;
	private float zAccel;

	/* And here the previous ones */
	private float xPreviousAccel;
	private float yPreviousAccel;
	private float zPreviousAccel;

	/* Used to suppress the first shaking */
	private boolean firstUpdate = true;

	/*What acceleration difference would we assume as a rapid movement? */
	private final float shakeThreshold = 1.5f;
	
	/* Has a shaking motion been started (one direction) */
	private boolean shakeInitiated = false;

I hope that the names and comments do explain enough about what is stored in these variables, if not, it will become clearer in the next steps. Now let us connect to the hardware sensors and wire up for their events, onCreate is the perfect place to do so.

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		mySensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); // (1)
		mySensorManager.registerListener(mySensorEventListener, mySensorManager
				.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
				SensorManager.SENSOR_DELAY_NORMAL); // (2)
	}

At (1) we get a reference to Android's sensor service, at (2) we register the previously defined SensorEventListener with the service. More precisely, we register only for events of the accelerometer and for a normal update rate - this could be changed, if we needed to be more precise.

Now let us define what we want to do when new sensor date arrives. We have yet defined a stub for SensorEventListeners method onSensorChanged, now we will fill it with some life.

    public void onSensorChanged(SensorEvent se) {
        updateAccelParameters(se.values[0], se.values[1], se.values[2]);   // (1)
        if ((!shakeInitiated) && isAccelerationChanged()) {                                      // (2) 
	    shakeInitiated = true; 
	} else if ((shakeInitiated) && isAccelerationChanged()) {                              // (3)
	    executeShakeAction();
	} else if ((shakeInitiated) && (!isAccelerationChanged())) {                           // (4)
	    shakeInitiated = false;
	}
    }

Once again into the details:

At (1) we copy the values of acceleration which we received from the SensorEvent into our state variables. The corresponding method is declared like this:

        /* Store the acceleration values given by the sensor */
	private void updateAccelParameters(float xNewAccel, float yNewAccel,
			float zNewAccel) {
                /* we have to suppress the first change of acceleration, it results from first values being initialized with 0 */
		if (firstUpdate) {  
			xPreviousAccel = xNewAccel;
			yPreviousAccel = yNewAccel;
			zPreviousAccel = zNewAccel;
			firstUpdate = false;
		} else {
			xPreviousAccel = xAccel;
			yPreviousAccel = yAccel;
			zPreviousAccel = zAccel;
		}
		xAccel = xNewAccel;
		yAccel = yNewAccel;
		zAccel = zNewAccel;
	}

At (2) we test for a rapid change of acceleration and whether any has happened before; if not, we store the information that now has happened.

At (3) we test again for a rapid change of acceleration, this time with another on before. If this is true, we can assume a shaking movement according to our definition and commence action.

At last at (4) we reset if we detected shaking before but do not get a rapid change of acceleration any more.

To complete the code, we add the last two methods, at first isAccelerationChanged().

	/* If the values of acceleration have changed on at least two axises, we are probably in a shake motion */
	private boolean isAccelerationChanged() {
		float deltaX = Math.abs(xPreviousAccel - xAccel);
		float deltaY = Math.abs(yPreviousAccel - yAccel);
		float deltaZ = Math.abs(zPreviousAccel - zAccel);
		return (deltaX > shakeThreshold && deltaY > shakeThreshold)
				|| (deltaX > shakeThreshold && deltaZ > shakeThreshold)
				|| (deltaY > shakeThreshold && deltaZ > shakeThreshold);
	}

Here we compare the current values of acceleration with the previous ones, if at least two of them have changed above our threshold, we return true.

The last method is executeShakeAction() which does whatever we wish to do when being shaken.

        private void executeShakeAction() {
		/* Save the cheerleader, save the world 
		   or do something more sensible... */
	}
No records found.