Advertisement
  1. Code
  2. Android SDK

Android Sensors in Depth: Proximity and Gyroscope

Scroll to top
Read Time: 14 min

Why always expect users to tap buttons on their touchscreens? By using some of the hardware sensors available on mid-range Android phones today, you can create apps that offer far more engaging user experiences.

The sensor framework, which is a part of the Android SDK, allows you to read raw data from most sensors, be they hardware or software, in an easy and consistent manner. In this tutorial, I'll show you how to use the framework to read data from two very common sensors: proximity and gyroscope. I'll also introduce you to the rotation vector sensor, a composite sensor that can, in most situations, serve as an easier and more accurate alternative to the gyroscope.

You can get an overview of Android's hardware sensors by reading the following tutorial:

Prerequisites

To follow along, you'll need the following:

  • An Android device with a proximity sensor and a gyroscope
  • The latest version of Android Studio

1. Project Setup

If your app is simply unusable on devices that do not have all the hardware sensors it needs, it should not be installable on such devices. You can let Google Play and other app marketplaces know about your app's hardware requirements by adding one or more <uses-feature> tags to your Android Studio project's manifest file.

The app we'll be creating in this tutorial will not work on devices that lack a proximity sensor and a gyroscope. Therefore, add the following lines to your manifest file:

1
<uses-feature
2
    android:name="android.hardware.sensor.proximity"
3
    android:required="true" />
4
<uses-feature
5
    android:name="android.hardware.sensor.gyroscope"
6
    android:required="true" />

The manifest file should look something like this now:

1
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
2
    xmlns:tools="http://schemas.android.com/tools"
3
    package="com.tutsplus.sensors">
4
    <application
5
            android:allowBackup="true"
6
            ....
7
    </application>
8
    <uses-feature
9
        android:name="android.hardware.sensor.proximity"
10
        android:required="true" />
11
    <uses-feature
12
        android:name="android.hardware.sensor.gyroscope"
13
        android:required="true" />
14
</manifest>

Note, however, that because the <uses-feature> tag doesn't help if a user installs your app manually using its APK file, you must still programmatically check if a sensor is available before using it.

2. Using the Proximity Sensor

In order to avoid accidental touch events, your phone's touchscreen goes black during calls, when it's very close to your ear. Ever wondered how your phone determines whether or not it is close to your ear? Well, it uses the proximity sensor, which is a hardware sensor that can tell if an object is close to it. Some proximity sensors can also tell how far away the object is, though their maximum range is usually only about 5 cm.

Let us now create an activity whose background color changes to red every time you hover your hand over your device's proximity sensor.

Step 1: Acquire the Proximity Sensor

To get access to any hardware sensor, you need a SensorManager object. To create it, use the getSystemService() method of your Activity class and pass the SENSOR_SERVICE constant to it.

1
SensorManager sensorManager =
2
        (SensorManager) getSystemService(SENSOR_SERVICE);

You can now create a Sensor object for the proximity sensor by calling the getDefaultSensor() method and passing the TYPE_PROXIMITY constant to it.

1
Sensor proximitySensor =
2
        sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

Before proceeding, always make sure that the Sensor object is not null. If it is, it means that the proximity sensor is not available.

1
if(proximitySensor == null) {
2
    Log.e(TAG, "Proximity sensor not available.");
3
    finish(); // Close app

4
}

All this code needs to be inside the onCreate() method of your activity. So it should look something like this now:

1
@Override
2
protected void onCreate(Bundle savedInstanceState) {
3
    super.onCreate(savedInstanceState);
4
    setContentView(R.layout.activity_main);
5
6
    sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
7
    proximitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
8
9
    sensorName = findViewById(R.id.name);
10
    sensorAccuracy = findViewById(R.id.accuracy);
11
    sensorTimestamp = findViewById(R.id.timestamp);
12
    sensorDistance = findViewById(R.id.distance);
13
14
15
    if(proximitySensor == null) {
16
        Log.e(TAG, "Proximity sensor not available.");
17
        finish(); // Close app

18
    }
19
}

We have added a few TextView objects to see all the information returned by the sensor. The calls to getSystemService() and getDefaultSensor() need to be inside the onCreate() method. Otherwise, your application will keep crashing because system services are not available to any activity before onCreate(). You might also want to read this tutorial on the Android Activity Lifecycle to learn more.

Step 2: Register a Listener

To be able to read the raw data generated by a sensor, you must associate a SensorEventListener with it by calling the registerListener() method of the SensorManager object. While doing so, you must also specify how frequently the data should be read from the sensor.

The SensorEventListener interface has two methods:

  1. the onSensorChanged() method, which is called whenever there is a new sensor event
  2. the onAccuracyChanged() method, which is called whenever there is a change in accuracy

It is important to remember that onSensorChanged() will be called regardless of any changes in the value of sensor data.

The SensorEvent object passed to onSensorChanged() contains additional information about the event. This information includes the sensor accuracy, which can be low (1), medium (2), or high (3). You can access the name of the sensor that generated this event by making a call to sensor.getName(). The timestamp when the sensor event occurred can be accessed by using the timestamp property. The distance measured by the proximity sensor can be accessed by using the values property, which returns an array. However, the proximity sensor contains a single value that depicts the distance, so we can get it by using values[0]

The following code creates a listener that allows you to read the proximity sensor's data once every two seconds:

1
private SensorEventListener proximitySensorListener = new SensorEventListener() {
2
    @Override
3
    public void onSensorChanged(SensorEvent sensorEvent) {
4
5
        sensorName.setText("Sensor Name: " + sensorEvent.sensor.getName());
6
        sensorAccuracy.setText("Accuracy: " + String.valueOf(sensorEvent.accuracy));
7
        sensorTimestamp.setText("Timestamp: " + String.valueOf(sensorEvent.timestamp));
8
        sensorDistance.setText("Distance: " + String.valueOf(sensorEvent.values[0]));
9
    }
10
11
    @Override
12
    public void onAccuracyChanged(Sensor sensor, int i) {
13
    }
14
};

Place this code directly inside the MainActivity class so that the listener is available inside all activity lifecycle methods.

I suggest you always register the listener inside the onResume() method of your activity and unregister it inside the onPause() method. Here's how you can unregister the listener:

1
@Override
2
protected void onResume() {
3
    super.onResume();
4
5
    sensorManager.registerListener(proximitySensorListener, proximitySensor, 2 * 1000 * 1000);
6
}
7
8
@Override
9
protected void onPause() {
10
    super.onPause();
11
    sensorManager.unregisterListener(proximitySensorListener);
12
}

Step 3: Use the Raw Data

As I mentioned earlier, the SensorEvent object, which is available inside the onSensorChanged() method, has a values array containing all the raw data generated by the associated sensor. In the case of the proximity sensor, the array contains a single value specifying the distance between the sensor and a nearby object in centimeters.

If the value is equal to the maximum range of the sensor, it's safe to assume that there's nothing nearby. Conversely, if it is less than the maximum range, it means that there is something nearby. You can determine the maximum range of any hardware sensor using the getMaximumRange() method of the associated Sensor object.

Some sensors might not report the exact distance but only a binary far or near measurement. In such cases, the sensor should report the maximum range value for far measurement and a lesser value such as 0 for near measurement.

To change the background color of the activity based on the proximity sensor's data, you can use the setBackgroundColor() method of the top-level window's decor view.

Accordingly, add the following code inside the onSensorChanged() method you created in the previous step:

1
if(sensorEvent.values[0] < proximitySensor.getMaximumRange()) {
2
    // Detected something nearby

3
    getWindow().getDecorView().setBackgroundColor(getResources().getColor(R.color.red_300));
4
} else {
5
    // Nothing is nearby

6
    getWindow().getDecorView().setBackgroundColor(getResources().getColor(R.color.green_300));
7
}

If you run the app now and hover your hand close to the top edge of your phone, you should see the screen turn red.

Color Change in Android Due to Proximity SensorColor Change in Android Due to Proximity SensorColor Change in Android Due to Proximity Sensor

3. Using the Gyroscope

The gyroscope allows you to determine the angular velocity of an Android device at any given instant. In simpler terms, it tells you how fast the device is rotating around its X, Y, and Z axes. Lately, even budget phones are being manufactured with a gyroscope built in, what with augmented reality and virtual reality apps becoming so popular.

By using the gyroscope, you can develop apps that can respond to minute changes in a device's orientation. To see how, let's now create an activity whose background color changes to purple every time you rotate the phone in the anticlockwise direction along the Z axis, and to yellow otherwise.

Step 1: Acquire the Gyroscope

To create a Sensor object for the gyroscope, all you need to do is pass the TYPE_GYROSCOPE constant to the getDefaultSensor() method of the SensorManager object.

1
gyroscopeSensor = 
2
        sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

Step 2: Register a Listener

Creating a listener for the gyroscope sensor is no different from creating one for the proximity sensor. While registering it, however, you must make sure that its sampling frequency is very high.

There are four different constants that you can use to specify the event listener frequency. These are:

  1. SENSOR_DELAY_NORMAL, which is suitable for screen orientation changes
  2. SENSOR_DELAY_UI, which is suitable for UI-related changes
  3. SENSOR_DELAY_GAME, which is suitable for creating the optimum gaming experience
  4. SENSOR_DELAY_FASTEST, which gives back sensor data as fast as possible

In our case, the most suitable option would be the SENSOR_DELAY_NORMAL constant.

1
// Create a listener

2
private SensorEventListener gyroscopeSensorListener = new SensorEventListener() {
3
    @Override
4
    public void onSensorChanged(SensorEvent sensorEvent) {
5
        // More code goes here

6
    }
7
8
    @Override
9
    public void onAccuracyChanged(Sensor sensor, int i) {
10
    }
11
};
12
13
@Override
14
protected void onResume() {
15
    super.onResume();
16
    sensorManager.registerListener(gyroscopeSensorListener, gyroscopeSensor, SensorManager.SENSOR_DELAY_NORMAL);
17
}
18
19
@Override
20
protected void onPause() {
21
    super.onPause();
22
    sensorManager.unregisterListener(gyroscopeSensorListener);
23
}

Step 3: Use the Raw Data

The gyroscope sensor's raw data consists of three float values, specifying the angular velocity of the device along its local X, Y, and Z axes. The unit of each value is radians per second. One radian is approximately 57°, so a rotational velocity of 1 radian per second translates to a rotation of about 57 degrees per second.

In the case of anticlockwise rotation along any axis, the value associated with that axis will be positive. Keep in mind that the rotation has to be anti-clockwise from your point of view if you are at a positive value along the X, Y, or Z axis. In the case of clockwise rotation, it will be negative.

The rotation speeds along the X, Y, and Z axes are given by the first, second, and third values in the values array.

Because we are currently interested only in rotation along the Z-axis, we'll be working only with the third element in the values array of the SensorEvent object. If it's more than 0.5f, we can, to a large extent, be sure that the rotation is anticlockwise and set the background color to purple. Similarly, if it's less than -0.5f, we can set the background color to yellow.

1
if(sensorEvent.values[2] > 0.5f) { // anticlockwise

2
    getWindow().getDecorView().setBackgroundColor(getResources().getColor(R.color.purple_300));
3
} else if(sensorEvent.values[2] < -0.5f) { // clockwise

4
    getWindow().getDecorView().setBackgroundColor(getResources().getColor(R.color.yellow_300));
5
}

If you run the app now, hold your phone in portrait mode, and tilt it to the left, you should see the activity turn purple. If you tilt it in the opposite direction, it should turn yellow.

Color Change in Android due to Gyroscope SensorColor Change in Android due to Gyroscope SensorColor Change in Android due to Gyroscope Sensor

If you turn the phone too much, however, its screen orientation will change to landscape and your activity will restart. To avoid this condition, I suggest you set the screenOrientation of the activity to portrait in the manifest file.

1
<activity
2
    android:name=".GyroscopeActivity"
3
    android:screenOrientation="portrait">
4
</activity>

4. Using the Rotation Vector Sensor

Most developers today prefer software composite sensors over hardware sensors. A software sensor combines low-level, raw data from multiple hardware sensors to generate new data that is not only easy to use, but also more accurate. The proximity sensor has no software alternative. The gyroscope, however, has two: game rotation vector sensor and rotation vector sensor. In this tutorial, we'll focus only on the latter.

In the example in the previous section, we changed the activity's background color every time the angular velocity along the Z-axis was more than 0.5 rad/s in the clockwise or anticlockwise direction. Working with angular velocities, however, is not intuitive. Furthermore, we had no idea what the actual angle of the device was before or after the rotation.

By using the rotation vector sensor, let us now create an activity whose background color changes only when it's rotated by a specific angle. For example, we could turn it yellow every time its rotation—along the Z-axis—is more than 45°, white when its rotation is between -10° and 10°, and purple when its rotation is less than -45°.

Step 1: Set Up the Rotation Vector Sensor

To acquire the rotation vector sensor, you must pass the TYPE_ROTATION_VECTOR constant to the getDefaultSensor() method of the SensorManager object.

1
rotationVectorSensor =
2
        sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);

Working with a software sensor is no different from working with a hardware one. Therefore, you must now associate a listener with the rotation vector sensor to be able to read its data. You can again use the SENSOR_DELAY_NORMAL constant for the polling interval.

1
// Create a listener

2
rvListener = new SensorEventListener() {
3
    @Override
4
    public void onSensorChanged(SensorEvent sensorEvent) {
5
        // More code goes here        

6
    }
7
8
    @Override
9
    public void onAccuracyChanged(Sensor sensor, int i) {
10
    }
11
};
12
13
// Register it

14
sensorManager.registerListener(rvListener,
15
            rotationVectorSensor, SensorManager.SENSOR_DELAY_NORMAL);

Step 2: Use the Data

The rotation vector sensor combines raw data generated by the gyroscope, accelerometer, and magnetometer to create a quaternion. Consequently, the values array of its SensorEvent object has the following five elements:

  • The X, Y, Z, and W components of the quaternion
  • A heading accuracy

You can convert the quaternion into a rotation matrix, a 4x4 matrix, by using the getRotationMatrixFromVector() method of the SensorManager class.

1
float[] rotationMatrix = new float[16];
2
SensorManager.getRotationMatrixFromVector(
3
        rotationMatrix, sensorEvent.values);

If you are developing an OpenGL app, you can use the rotation matrix directly to transform objects in your 3D scene. For now, however, let us convert the rotation matrix into an array of orientations, specifying the rotation of the device along the Z, X, and Y axes. To do so, we can use the getOrientation() method of the SensorManager class.

Before you call the getOrientation() method, you must remap the coordinate system of the rotation matrix. More precisely, you must rotate the rotation matrix such that the Z-axis of the new coordinate system coincides with the Y-axis of the original coordinate system.

1
// Remap coordinate system

2
float[] remappedRotationMatrix = new float[16];
3
SensorManager.remapCoordinateSystem(rotationMatrix,
4
        SensorManager.AXIS_X,
5
        SensorManager.AXIS_Z,
6
        remappedRotationMatrix);
7
8
// Convert to orientations

9
float[] orientations = new float[3];
10
SensorManager.getOrientation(remappedRotationMatrix, orientations);

By default, the orientations array contains angles in radians instead of degrees. If you are accustomed to radians, feel free to use it directly. Otherwise, use the following code to convert all its angles to degrees:

1
for(int i = 0; i < 3; i++) {
2
    orientations[i] = (float)(Math.toDegrees(orientations[i]));
3
}

You can now change the background color of the activity based on the third element of the orientations array.

1
sensorAngle.setText(Float.toString(Math.round(orientations[2])));
2
3
if(orientations[2] > 45) {
4
    getWindow().getDecorView().setBackgroundColor(getResources().getColor(R.color.yellow_300));
5
} else if(orientations[2] < -45) {
6
    getWindow().getDecorView().setBackgroundColor(getResources().getColor(R.color.purple_300));
7
} else if(Math.abs(orientations[2]) < 10) {
8
    getWindow().getDecorView().setBackgroundColor(Color.WHITE);
9
}

If you run the app now, hold your phone in portrait mode, and tilt it by more than 45° clockwise or anticlockwise, you should see the background color change. We also change the text inside sensorAngle to reflect the new angle of rotation.

Rotation Angle Calculated Using Rotation SensorRotation Angle Calculated Using Rotation SensorRotation Angle Calculated Using Rotation Sensor

Conclusion

In this tutorial, you learned how to use Android's sensor framework to create apps that can respond to data generated by the proximity sensor and the gyroscope. You also learned how to work with the rotation vector sensor, a more popular alternative to the gyroscope. Feel free to use the sensors in creative ways. Be aware, though, that apps that use sensors inefficiently can drain a device's battery very quickly.

To learn more about hardware sensors and the data they generate, you can refer to the official sensors API guide. And check out some of our other hardware and sensor content here on Envato Tuts+!

This post has been updated with contributions from Nitish Kumar. Nitish is a web developer with experience in creating eCommerce websites on various platforms. He spends his free time working on personal projects that make his everyday life easier or taking long evening walks with friends.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.