Hour 10. Using the Accelerometer


What You’ll Learn in This Hour

• About the Android sensors

• How to access Android sensors

• How to disable screen rotation

• How to use the accelerometer


This hour covers the broader subject of Android sensor programming, tapping into the hardware sensors included in most Android devices (smartphones and tablets). The emphasis of this hour will be on the most common sensor: the accelerometer. We’ll use the code we study in this hour in the next few hours to assist with using other common Android sensors, such as the ambient light sensor, air pressure sensor, and others.

Android Sensors

Android devices may or may not come with all of the sensors supported by OS 4.0. The SDK supports the following types of sensors (which are self-explanatory):

• TYPE_ACCELEROMETER

• TYPE_AMBIENT_TEMPERATURE

• TYPE_GRAVITY

• TYPE_GYROSCOPE

• TYPE_LIGHT

• TYPE_LINEAR_ACCELERATION

• TYPE_MAGNETIC_FIELD

• TYPE_PRESSURE

• TYPE_PROXIMITY

• TYPE_RELATIVE_HUMIDITY

• TYPE_ROTATION_VECTOR

Most Android devices have an accelerometer sensor, but all the other types of sensors are optional and up to the manufacturer. Many devices will also have a compass (also called a magnetic field sensor).


By the Way

A GPS location service is not a sensor; it is a different hardware component not part of this list.


Accessing the Sensors

Access to the accelerometer and other sensors on an Android device is provided by an interface class called android.hardware.SensorEventListener. This class must be implemented using the implements statement, in the same manner that we added support for touchscreen input. Here is an example:

public class Game extends Activity implements SensorEventListener

Just note that you will need a separate android.hardware.SensorManager variable for each individual sensor you plan to use in an Android program.

A variable is defined and then the object is created from within onCreate(). First, the definition:

SensorManager sensors;

Now, the object is created in onCreate():

sensors = (SensorManager)getSystemService(SENSOR_SERVICE);

All the device’s sensors send their status changes to the program using the SensorEventListener interface class, which has two methods we have to implement:

onSensorChanged()

onAccuracyChanged()

Following is the definition of the onSensorChanged() method that is required to obtain input from the sensors. This one method receives all sensor events.

@Override public void onSensorChanged(SensorEvent event) {
    // . . .
}

Here is the definition of the onAccuracyChanged() method. As its name implies, this method receives notices of accuracy state changes that may occur in the sensor devices.

@Override public void onAccuracyChanged(Sensor arg0, int arg1) {
    // . . .
}

The sensor changes are reported in the SensorEvent parameter passed to onSensorChanged(), so this is the parameter we want to examine further.


Watch Out

Sensors will continue to function, even when the Android device is in suspend mode, until you specifically disable them during the pause event!


Disabling Screen Orientation Changes

To test the accelerometer sensor, you will have to disable the screen autorotation that changes from portrait to landscape based on how you are holding your Android device. Phones will default to portrait mode, whereas tablets will default to landscape. Many games will make sense only when running in landscape mode, which is the preferred orientation for games running on a PC or video game console. Most games are designed to run inside a specific screen boundary and cannot adapt dynamically.

Changing the screen specifications at runtime is like changing the computer hardware itself. For best performance, and best gameplay, the hardware needs to remain consistent while a game is running. As a game programmer, you have enough to do already without being concerned with hardware changing at runtime. See Figure 10.1 for an example of what happens to a game when the orientation flips from landscape to portrait.

Image

Figure 10.1. Screen autorotation is a serious problem for a game.

First, import the necessary namespace:

import android.content.pm.ActivityInfo;

Add this line to onCreate() to force your Android app to stay in either landscape or portrait mode and not change:

setRequestedOrientation(ActivityInfo. SCREEN_ORIENTATION_LANDSCAPE );

Here are the two constants that you can use:

• ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE

• ActivityInfo.SCREEN_ORIENTATION_PORTRAIT

Accelerometer Initialization

We already covered the SensorManager object and will need to use it to create an accelerometer sensor object. This can be defined as type android.hardware.Sensor.

Sensor accel;

Now, assuming the sensors object was previously created (and therefore available), we can create the accelerometer object like so:

accel = sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);


Watch Out

It is essential to disable and re-enable sensor devices when the Android pauses and resumes your app or game! Failure to do so results in an exception error or a program lockup. Live sensors will also drain the battery, even if the device is suspended.


The next two steps are crucial to keeping the program stable. Live sensors also drain the battery—even when the device is in suspend mode.

Without these two steps, your accelerometer code will crash the program! We must disable the accelerometer in response to the onPause() event and reinitialize it in response to the onResume() event. Failure to do so results in a frozen program (if it doesn’t crash first with an exception error).

To disable a sensor, call SensorManager.unregisterListener(). To reinitialize a sensor, call SensorManager.registerListener(). There are several versions of each method, and we’ll look at the most common ones in the following code.

@Override public void onPause() {
    super.onPause();
    sensors.unregisterListener(this);
}

@Override public void onResume() {
    super.onResume();
    sensors.registerListener(this, accel, SensorManager.
            SENSOR_DELAY_NORMAL);
}

Having the register and unregister calls in place at these two crucial events in the program code results in a well-behaved program.

Accelerometer Movement

The accelerometer reports the tilting movement of the device in three axes, although we are concerned only with the two dimensions of X and Y. Tilting the device forward (away from you) results in a negative Y. Tilting backward (toward you as if to read a book) produces a positive Y. The X axis increases to the left and decreases to the right. See Figure 10.2.

Image

Figure 10.2. Accelerometer axis directions.

All sensors report changes using the same onSensorChanged() method, and that is also the case for the accelerometer. The SensorEvent parameter has an internal sensor object with a method called getType(), and this is what we use to determine which sensor is reporting an update. Here is an example:

@Override public void onSensorChanged(SensorEvent event) {
    switch (event.sensor.getType()) {
    case Sensor.TYPE_ACCELEROMETER:
        //save the accelerometer motion values
        accelMotion.x = event.values[0];
        accelMotion.y = event.values[1];
        accelMotion.z = event.values[2];
        break;
    }
}

The accelMotion variable is previously defined, like so:

Float3 accelMotion = new Float3();

Float3 is a helpful class that contains an x, y, and z, as properties that work well for storing coordinate data. Oddly enough, Float3 is not a normal Java language support class; it’s unique to the Android SDK. The actual namespace is android.renderscript. You may find this a useful resource with many helpful classes, such as Float2, among others. These are classes normally used to pass data to and from rendering scripts (so-called shader fragments), but we can use them for other purposes as well.

Incidentally, four major namespaces are used for sensor programming, so you may find this list of import lines helpful:

import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

Getting a List of Available Sensors

You can detect which sensors are available using the method SensorManager.getSensorList(). This method returns a List<Sensor> containing the detected sensor objects. You can get the name of each sensor using the Sensor.getName() method.

To use this helper method, we’ll first need a container for the returned list of sensors:

List<Sensor> sensorList;

Next, in the program’s onCreate() method, we’ll retrieve the list of sensors from our SensorManager object:

sensorList = sensors.getSensorList(Sensor.TYPE_ALL);

To read the names out of the list, we can create an iterator and use a while loop, like so:

Iterator<Sensor> iter = sensorList.iterator();
while (iter.hasNext()) {
    Sensor sensor = iter.next();
    Log.d("Sensor", sensor.getName());
}

On a Toshiba Thrive 7″ tablet device, the following sensors were reported:

MPL rotation vector

MPL linear accel

MPL gravity

MPL Gyro

MPL accel

MPL magnetic field

MPL Orientation (android deprecated format)

Intersil is129018 Ambient Light Sensor

Intersil is129018 Proximity sensor

NCT1008 TEMPERATURE sensor

Rotation Vector Sensor

Gravity Sensor

Linear Acceleration Sensor

Orientation Sensor

Corrected Gyroscope Sensor

Because this is a fairly average Android 4 tablet, we can assume that most Android devices at this price/quality range and higher will have a similar set of sensors. However, some Androids configured as custom devices (such as the Amazon Kindle Fire) and low-end value models (costing under $150) may not have as many sensors.

Complete Example

Here is the source code for a complete example that shows how to use an accelerometer. This program displays the accelerometer motion data as X, Y, and Z values, and draws a large circle that moves on the screen in response to motion. The green circle will seem to “roll” on the screen like a billiard ball on a tilting table. Also displayed is the list of sensors detected on the device. Figure 10.3 shows the output from the program.

Image

Figure 10.3. The Accelerometer Demo program.

package android.program;
import java.util.*;
import android.app.Activity;
import android.os.Bundle;
import android.content.*;
import android.content.res.*;
import android.graphics.*;
import android.view.*;
import android.content.pm.ActivityInfo;
import android.renderscript.*;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;

public class Game extends Activity implements SensorEventListener {
    DrawView drawView;
    SensorManager sensors;
    Sensor accel;
    Float3 accelMotion = new Float3();
    List<Sensor> sensorList;

    @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        drawView = new DrawView(this);
        setContentView(drawView);

        //prevent orientation changes--set explictly
        setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);

        //initialize accelerometer
        sensors = (SensorManager)getSystemService(SENSOR_SERVICE);
        accel = sensors.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        //get list of sensors on this device
        sensorList = sensors.getSensorList(Sensor.TYPE_ALL);
    }

    @Override public void onSensorChanged(SensorEvent event) {
        switch (event.sensor.getType()) {
        case Sensor.TYPE_ACCELEROMETER:
            //save the accelerometer motion values
            accelMotion.x = event.values[0];
            accelMotion.y = event.values[1];
            accelMotion.z = event.values[2];
            break;
        }
    }

    @Override public void onAccuracyChanged(Sensor arg0, int arg1) {
    }

    @Override public void onResume() {
        super.onResume();
        drawView.resume();
        sensors.registerListener(this, accel, SensorManager.
            SENSOR_DELAY_NORMAL);
    }

    @Override public void onPause() {
        super.onPause();
        drawView.pause();
        sensors.unregisterListener(this);
    }

    public class DrawView extends SurfaceView implements Runnable
    {
        Thread gameloop = null;
        SurfaceHolder surface = null;
        volatile boolean running = false;
        AssetManager assets = null;
        Paint paint = new Paint();
        Float2 center;

        //constructor method
        public DrawView(Context context) {
            super(context);
            surface = getHolder();
            assets = context.getAssets();
        }

        public void resume() {
            running = true;
            gameloop = new Thread(this);
            gameloop.start();
        }

        public void pause() {
            running = false;
            while (true) {
                try {
                    gameloop.join();
                }
                catch (InterruptedException e) { }
            }
        }

        //thread run method
        @Override public void run() {

            while (running) {
                if (!surface.getSurface().isValid()) continue;

                //open the canvas for drawing
                Canvas canvas = surface.lockCanvas();

                //clear the screen
                canvas.drawColor(Color.BLACK);

                //calculate center of screen
                float width = canvas.getWidth();
                float height = canvas.getHeight();
                center = new Float2(width/2.0f, height/2.0f);

                //draw circle at center, adjusted for movement
                Float2 ratio = new Float2(width/10.0f, height/10.0f);
                float x = center.x - accelMotion.x * ratio.x;
                float y = center.y + accelMotion.y * ratio.y;
                paint.setColor(Color.GREEN);
                canvas.drawCircle(x, y, 100, paint);

                //display accelerometer info
                paint.setColor(Color.WHITE);
                paint.setTextSize(24);
                canvas.drawText("Accelerometer Demo", 10, 20, paint);
                String s = "X=" + Math.round(accelMotion.x) + ", Y=" +
                    accelMotion.y + ", Z=" + accelMotion.z;
                canvas.drawText(s, 10, 50, paint);
                canvas.drawText("Model: " + accel.getVendor(), 10, 80, paint);

                //parse and print out the sensor names
                int texty = 150;
                Iterator<Sensor> iter = sensorList.iterator();
                while (iter.hasNext()) {
                    Sensor sensor = iter.next();
                    canvas.drawText(sensor.getName(), 10, texty, paint);
                    texty += 30;
                }

                //close the canvas
                surface.unlockCanvasAndPost(canvas);

                try {
                    Thread.sleep(20);
                }
                catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Summary

The sensors on an Android device are a lot of fun to play around with from the programming point of view! There are so many sensors that we can tap into. Although not every Android device will support all the sensors, we can at least learn to write the code needed to read from the various sensors in preparation for a game that might use them in creative ways. In the next hour, we explore several more sensors to get a good grasp of the subject and to learn how they might be used in a real-world setting.

Q&A

Q. Some sensors are pretty much guaranteed to be present on every Android device, especially from OS 4.0 and later. Which of the sensors would you always support by default for all your games?

A. A list of sensors should be provided. There is no wrong answer.

Q. Sensor input gives a game designer some fascinating new possibilities for making an immersive game. What are some interesting game designs that could take advantage of Android sensors?

A. Answers will vary.

Workshop

Quiz

1. What class provides support for the accelerometer sensor?

2. What interface class provides access to the sensor event method?

3. What type of value does the accelerometer return for X and Y?

Answers

1. Sensor

2. SensorEventListener

3. Float

Activities

Do you know which sensors are available on your own personal Android device? Run the Accelerometer Demo program to find out. How does the list of detected sensors differ between your Android and the Toshiba Thrive featured earlier in the hour?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.129.194