Master Android’s sensors: hardware, software and multidimensional

0
204

Today, most modern Android devices are kitted out with a bunch of sensors.

You can use this information in lots of different ways – whether it’s monitoring light levels so your application can automatically adjust its brightness or color scheme; allowing the user to interact with your mobile game using gestures such as tilting their device; or using the proximity sensor to automatically disable touch events whenever the user holds their device to their ear.

In this article, we’ll create three applications that retrieve light, proximity and motion data from a range of hardware and software sensors. We’ll also monitor these Android sensors in real time, so your application always has access to the latest information.

By the end of this article, you’ll know how to extract a single piece of data from a Android sensor, and how to handle sensors that provide their data in the form of a multidimensional array.

What Android sensors can I use?

Android sensors can be divided into the following categories:

  • Environmental sensors. These measure environmental conditions, such as air temperature, pressure, humidity and ambient light levels.

  • Position sensors. This category includes sensors that measures the device’s physical position, such as proximity sensors and geomagnetic field sensors.
    Motion sensors. These sensors measure device motion, and include accelerometers, gravity sensors, gyroscopes, and rotation vector sensors.

In addition, sensors can either be:

  • Hardware based. These are physical components that are built into the device and directly measure specific properties, such as acceleration or the strength of the surrounding geomagnetic fields.
  • Software based, sometimes known as virtual sensors or composite sensors. These typically collate data from multiple hardware-based sensors. Towards the end of this article, we’ll be working with the rotation vector sensor, which is a software sensor that combines data from the device’s accelerometer, magnetometer, and gyroscope.

Environmental sensors: Measuring ambient light

Android’s light sensor measures ambient light in “lux” units, which is the intensity of light as perceived by the human eye. The lux value reported by a sensor can vary across devices, so if your application requires consistent values then you may need to manipulate the raw data before using it in your application.

In this section, we’re going to create an application that retrieves the current lux value from the device’s light sensor, displays it in a TextView, and then updates the TextView as new data becomes available. You can then use this information in a range of apps, for example you might create a torch application that pulls information from the light sensor and then automatically adjusts the strength of its beam based on the current light levels.

Create a new Android project with the settings of your choice, and let’s get started!

Displaying your sensor data

I’m going to add a TextView that’ll eventually display the data we’ve extracted from the light sensor. This TextView will update whenever new data becomes available, so the user always has access to the latest information.

Open your project’s activity_main.xml file, and add the following:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <TextView
       android:id="@+id/lightTextView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/light_sensor"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintHorizontal_bias="0.017"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_bias="0.022" />

</android.support.constraint.ConstraintLayout>

Next, we need to create the “light_sensor” string resource that’s referenced in our layout. Open your project’s strings.xml file, and add the following:

   <string name="light_sensor">Light Sensor: %1$.2f</string>

The “%1$.2f” is a placeholder that specifies the information we want to display, and how it should be formatted:

  • %1. You can insert multiple placeholders into the same string resource; “%1” indicates that we’re using a single placeholder.
  • $.2. This specifies how our application should format each incoming floating-point value. The “$.2” indicates that the value should be rounded to two decimal places.
  • F. Format the value as a floating-point number.

While some sensors are more common than others, you should never assume that every device has access to the exact same hardware and software. Sensor availability can even vary across different versions of Android, as some sensors weren’t introduced until later releases of the Android platform.

You can check whether a particular sensor is present on a device, using the Android sensor framework. You can then disable or enable parts of your application based on sensor availability, or you might display a message explaining that some of your application’s features won’t work as expected.

While we have our strings.xml file open, let’s create a “no_sensor” string, which we’ll display if the light sensor is unavailable:

   <string name="no_sensor">No light sensor available</string>

If your application cannot provide a good user experience without having access to a particular sensor, then you need to add this information to your Manifest. For example, if your app requires access to a compass sensor, then you can use the following:

<uses-feature android:name="android.hardware.sensor.compass"
  android:required="true" />

Now, your app can only be downloaded to devices that have a compass sensor.

While this may limit your audience, it’s far less damaging than allowing someone to download your application when they’re guaranteed to have a bad experience, due to their device’s sensor configuration.

Communicating with a sensor: SensorManager, SensorEvents, and listeners

To communicate with the device’s light sensor, you need to complete the following steps:

1. Obtain an instance of SensorManager

The SensorManager provides all the methods you need to access the device’s full range of sensors.

To start, create a variable that’ll hold an instance of SensorManager:

 private SensorManager lightSensorManager;

Then, you need to obtain an instance of SensorManager, by calling the Context.getSystemService method and passing in the Context.SENSOR_SERVICE argument:

       lightSensorManager = (SensorManager) getSystemService(
               Context.SENSOR_SERVICE);

2. Get a reference to lightTextView

Next, we need to create a private member variable that’ll hold our TextView objects, and assign it to our TextView:

 private TextView lightTextView;
...
...
...
    lightTextView = (TextView) findViewById(R.id.lightTextView);

3. Check whether the sensor exists on the current device

You can gain access to a particular sensor by calling the getDefaultSensor() method, and then passing it the sensor in question. The type constant for the light sensor is TYPE_LIGHT, so we need to use the following:

       lightSensor = lightSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

If the sensor doesn’t exist on this device, then the getDefaultSensor() method will return null, and we’ll display the “no_sensor” string:

  String sensor_error = getResources().getString(R.string.no_sensor);
       if (lightSensor == null) { lightTextView.setText(sensor_error); }

   }

4. Register your sensor listeners

Every time a sensor has new data, Android generates a SensorEvent object. This SensorEvent object includes the sensor that generated the event, a timestamp, and the new data value.

Initially, we’ll be focusing on the light and proximity sensors, which return a single piece of data. However, some sensors provide multidimensional arrays for each SensorEvent, including the rotation vector sensor, which we’ll be exploring towards the end of this article.

To ensure our application is notified about these SensorEvent objects, we need to register a listener for that specific sensor event, using SensorManager’s registerListener().

The registerListener() method takes the following arguments:

  • An app or Activity Context.
  • The type of Sensor that you want to monitor.
  • The rate at which the sensor should send new data. A higher rate will provide your application with more data, but it’ll also use more system resources, especially battery life. To help preserve the device’s battery, you should request the minimum amount of data that your application requires. I’m going to use SensorManager.SENSOR_DELAY_NORMAL, which sends new data once every 200,000 microseconds (0.2 seconds).

Since listening to a sensor drains the device’s battery, you should never register listeners in your application’s onCreate() method, as this will cause the sensors to continue sending data, even when your application is in the background.

Instead, you should register your sensors in the application’s onStart() lifecycle method:

   @Override
    protected void onStart() {
        super.onStart();

//If the sensor is available on the current device...//

        if (lightSensor != null) {

//….then start listening//

            lightSensorManager.registerListener(this, lightSensor,
                    SensorManager.SENSOR_DELAY_NORMAL);
        }
    }

5. Implement the SensorEventListener callbacks

SensorEventListener is an interface that receives notifications from the SensorManager
whenever new data is available, or the sensor’s accuracy changes.

The first step, is modifying our class signature to implement the SensorEventListener interface:

public class MainActivity extends AppCompatActivity
       implements SensorEventListener {

We then need to implement the following callback methods:

onSensorChanged()

This method is called in response to each new SensorEvent.

Sensor data can often change rapidly, so your application may be calling the onSensorChanged() method on a regular basis. To help keep your application running smoothly, you should perform as little work as possible inside the onSensorChanged() method.

@Override
    public void onSensorChanged(SensorEvent sensorEvent) {

//To do//

    }
onAccuracyChanged()

If the sensor’s accuracy improves or declines, then Android will call the onAccuracyChanged() method and pass it a Sensor object containing the new accuracy value, such as SENSOR_STATUS_UNRELIABLE or SENSOR_STATUS_ACCURACY_HIGH.

The light sensor doesn’t report accuracy changes, so I’ll be leaving the onAccuracyChanged() callback empty:

   @Override
    public void onAccuracyChanged(Sensor sensor, int i) {

//To do//

    }
}

6. Retrieve the sensor value

Whenever we have a new value, we need to call the onSensorChanged() method, and retrieve the “light_sensor” string. We can then override the string’s placeholder text (%1$.2f) and display the updated string as part of our TextView:

@Override

   public void onSensorChanged(SensorEvent sensorEvent) {

//The sensor’s current value//

       float currentValue = sensorEvent.values[0];

//Retrieve the “light_sensor” string, insert the new value and display it to the user//

    lightTextView.setText(getResources().getString(
               R.string.light_sensor, currentValue));
   }

7. Unregister your listeners

Sensors can generate large amounts of data in a small amount of time, so to help preserve the device’s resources you’ll need to unregister your listeners when they’re no longer needed.

To stop listening for sensor events when your application is in the background, add unregisterListener() to your project’s onStop() lifecycle method:

   @Override
    protected void onStop() {
        super.onStop();
        lightSensorManager.unregisterListener(this);
    }

Note that you shouldn’t unregister your listeners in onPause(), as in Android 7.0 and higher applications can run in split-screen and picture-in-picture mode, where they’re in a paused state, but remain visible onscreen.

Using Android’s light sensors: Completed code

After completing all the above steps, your project’s MainActivity should look something like this:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity

//Implement the SensorEventListener interface//

       implements SensorEventListener {

//Create your variables//

   private Sensor lightSensor;
   private SensorManager lightSensorManager;
   private TextView lightTextView;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       lightTextView = (TextView) findViewById(R.id.lightTextView);

//Get an instance of SensorManager//

       lightSensorManager = (SensorManager) getSystemService(
              Context.SENSOR_SERVICE);

//Check for a light sensor//

       lightSensor = lightSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);

//If the light sensor doesn’t exist, then display an error message//

       String sensor_error = getResources().getString(R.string.no_sensor);
       if (lightSensor == null) { lightTextView.setText(sensor_error); }

   }

   @Override
    protected void onStart() {
        super.onStart();

//If the sensor is available on the current device...//

       if (lightSensor != null) {

//….then register a listener//

       lightSensorManager.registerListener(this, lightSensor,

//Specify how often you want to receive new data//

                   SensorManager.SENSOR_DELAY_NORMAL);
       }
   }

   @Override
    protected void onStop() {
       super.onStop();

//Unregister your listener//

       lightSensorManager.unregisterListener(this);
   }

   @Override
    public void onSensorChanged(SensorEvent sensorEvent) {

//The sensor’s current value//

       float currentValue = sensorEvent.values[0];

//Retrieve the “light_sensor” string, insert the new value and update the TextView//

       lightTextView.setText(getResources().getString(
               R.string.light_sensor, currentValue));
}

   @Override

//If the sensor’s accuracy changes….//

    public void onAccuracyChanged(Sensor sensor, int i) {

//TO DO//

   }
}

Test your completed Android sensor app

To test this application on a physical Android smartphone or tablet:

  • Install the project on your device (by selecting “Run > Run” from the Android Studio toolbar).
  • Although it varies between devices, the light sensor is often located on the upper-right of the screen. To manipulate the light levels, move your device closer to, and then further away from a light source. Alternatively, you could try covering the device with your hand, to block out the light. The “Light Sensor” value should increase and decrease, depending on the amount of light available.

If you’re using an Android Virtual Device (AVD), then the emulator has a set of virtual sensor controls that you can use to simulate various sensor events. You access these virtual sensor controls, via the emulator’s “Extended Controls” window:

  • Install the application on your AVD.
  • Alongside the AVD, you’ll see a strip of buttons. Find the three-dotted “More” button (where the cursor is positioned in the following screenshot) and give it a click. This launches the “Extended Controls” window.

  • In the left-hand menu, select “Virtual sensors.”
  • Select the “Additional sensors” tab. This tab contains various sliders that you can use to simulate different position and environmental sensor events.

  • Find the “Light (lux)” slider and drag it left and right, to change the simulated light levels. Your application should display these changing values, in real time.

You can download the completed project from GitHub.

Measuring distance, with Android’s proximity sensors

Now we’ve seen how to retrieve information from an environmental sensor, let’s look at how you’d apply this knowledge to a position sensor.

In this section, we’ll use the device’s proximity sensor to monitor the distance between your smartphone or tablet, and other objects. If your application has any kind of voice functionality, then the proximity sensor can help you determine when the smartphone is being held to the user’s ear, for example when they’re having a telephone conversation. You can then use this information to disable touch events, so the user doesn’t accidentally hang up, or trigger other unwanted events mid-conversation.

Creating the user interface

I’m going to display the proximity data onscreen, so you can watch it update in real time. To help keep things simple, let’s reuse much of the layout from our previous application:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

 <TextView
       android:id="@+id/proximityTextView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:text="@string/proximity_sensor"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintHorizontal_bias="0.017"
       app:layout_constraintLeft_toLeftOf="parent"
       app:layout_constraintRight_toRightOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       app:layout_constraintVertical_bias="0.022" />

</android.support.constraint.ConstraintLayout>

Next, open your strings.xml file and create a “proximity_sensor” string. Once again, this string needs to contain a placeholder, which will eventually be populated by data extracted from the proximity sensor:

<resources>
   <string name="app_name">ProximitySensor</string>
   <string name="proximity_sensor">Proximity Sensor: %1$.2f</string>
   <string name="no_sensor">No proximity sensor available</string>
</resources>

Getting data from the proximity sensor

Similar to the light sensor, Android’s proximity sensor returns a single data value, which means we can reuse much of the code from our previous application. However, there are a few major differences, plus some name-related changes that make this code easier to follow:

  • Create an instance of SensorManager, which this time around I’m going to name “proximitySensorManager.”
  • Obtain an instance of “proximitySensorManager.”
  • Create a reference to the “proximityTextView.”
  • Call the getDefaultSensor() method, and pass it the TYPE_PROXIMITY sensor.
  • Register and unregister listeners for the proximity sensor.

After making these tweaks, you should end up with the following:

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorManager;
import android.hardware.SensorEventListener;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity

//Implement the SensorEventListener interface//

       implements SensorEventListener {

//Create your variables//

   private Sensor proximitySensor;
   private SensorManager proximitySensorManager;
   private TextView proximityTextView;

   @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        proximityTextView = (TextView) findViewById(R.id.proximityTextView);

//Get an instance of SensorManager//

        proximitySensorManager = (SensorManager) getSystemService(
                Context.SENSOR_SERVICE);

//Check for a proximity sensor//

       proximitySensor = proximitySensorManager.getDefaultSensor(
               Sensor.TYPE_PROXIMITY);

//If the proximity sensor doesn’t exist, then display an error message//

       String sensor_error = getResources().getString(R.string.no_sensor);

       if (proximitySensor == null) {
           proximityTextView.setText(sensor_error);
       }
   }

   @Override
   protected void onStart() {
       super.onStart();

//If the sensor is available on the current device...//

       if (proximitySensor != null) {

//….then register a listener//

           proximitySensorManager.registerListener(this, proximitySensor,

//Specify how often you want to receive new data//

                   SensorManager.SENSOR_DELAY_NORMAL);
       }

   }

   @Override
   protected void onStop() {
    super.onStop();

//Unregister your listener to preserve system resources//

       proximitySensorManager.unregisterListener(this);
   }

   @Override
   public void onSensorChanged(SensorEvent sensorEvent) {

//The sensor’s current value//

       float currentValue = sensorEvent.values[0];

//Retrieve the “proximity_sensor” string, insert the new value and update the TextView//

       proximityTextView.setText(getResources().getString(
               R.string.proximity_sensor, currentValue));
   }
    
   @Override

//If the sensor’s accuracy changes….//

   public void onAccuracyChanged(Sensor sensor, int i) {

//...TO DO//

   }
}

Testing: How close is the user to their device?

To put this application to the test on a physical Android smartphone or tablet, install the application on your device and then experiment by moving your hand towards the screen, and then moving it away again. The “Proximity Sensor” value should record your movements.

Just be aware that proximity sensors can vary between devices. Some devices may only display two proximity values – one to indicate “Near” and one to indicate “Far” – so don’t be surprised if you don’t see much variety on your physical Android device.

To test this application on an emulator:

  • Install your application on an AVD.
  • Find the three-dotted “More” button and give it a click, which launches the “Extended Controls” window.
  • In the window’s left-hand menu, select “Virtual sensors.”
  • Select the “Additional sensors” tab.
  • Find the “Proximity” slider, and drag it left and right to emulate an object moving closer to the device, and then further away. The “Proximity Sensor” values should change, as you manipulate the slider.

You can download the completed project from GitHub.

Motion sensors: Processing multidimensional arrays

Up until this point, we’ve focused on sensors that supply a single item of data, but there are some sensors that provide multidimensional arrays for each SensorEvent. These multidimensional sensors include motion sensors, which we’ll be focusing on in this final section.

Motion sensors can help you:

  • Provide an alternative method of user input. For example, if you’re developing a mobile game then the user might move their character around the screen by tilting their device.
  • Infer user activity. If you’ve created an activity-tracking app, then motion sensors can help you gauge whether the user is travelling in a car, jogging, or sitting at their desk.
  • More accurately determine orientation. It’s possible to extract coordinates from a device’s motion sensors, and then translate them based on the Earth’s coordinate system, to get the most accurate insight into the device’s current orientation.

In this final section, we’ll be using the rotation vector sensor (TYPE_ROTATION_VECTOR). Unlike the light and proximity sensors, this is a software sensor that collates data from the device’s accelerometer, magnetometer, and gyroscope sensors. Although working with this sensor often requires you to perform mathematical conversions and transformations, it can also provide you with a range of highly-accurate information about the device.

We’ll be creating an application that uses the rotation vector sensor to measure:

  • Pitch. This is the top-to-bottom tilt of the device.
  • Roll. This is the left-to-right tilt of the device.

Displaying real time pitch and roll data

Since we’re measuring two metrics, we need to create two TextViews and two corresponding string resources:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">

   <TextView
       android:id="@+id/pitchTextView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_margin="10dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="12dp"
       android:layout_marginEnd="8dp"
       android:text="@string/pitch_sensor"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent" />

   <TextView
       android:id="@+id/rollTextView"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="8dp"
       android:text="@string/roll_sensor"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toBottomOf="@+id/pitchTextView" />

</android.support.constraint.ConstraintLayout>

Open the strings.xml file, and add the following:

<resources>
   <string name="app_name">MotionSensors</string>
   <string name="pitch_sensor">Pitch Sensor: %1$.2f</string>
   <string name="roll_sensor">Roll Sensor: %1$.2f</string>
   <string name="no_sensor">No motion sensor available</string>
</resources>

Using the rotation vector sensor in your app

We’ll be re-using some of the code from our previous applications, so let’s focus on the areas where communicating with the rotation vector sensor, is significantly different to what we’ve seen before.

1. Use the TYPE_ROTATION_VECTOR

Since we’re working with the rotation vector sensor, we need to call the getDefaultSensor() method, and then pass it the TYPE_ROTATION_VECTOR constant:

positionSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);

2. Translate the sensor data

Unlike the previous light and proximity sensors, motion sensors return multidimensional arrays of sensor values for every SensorEvent. These values are formatted using the standard “X, Y, Z” coordinate system, which is calculated relative to the device when it’s held in its default, “natural” orientation.

Android doesn’t switch these X, Y and Z coordinates around to match the device’s current orientation, so the “X” axis will remain the same regardless of whether the device is in portrait or landscape mode. When using the rotation vector sensor, you may need to convert the incoming data to match the device’s current rotation.

Portrait is the default orientation for most smartphones, but you shouldn’t assume this is going to be the case for all Android devices, particularly tablets. In this article, we’ll use a rotation matrix to translate the sensor’s data from its original, device coordinate system, to the Earth’s coordinate system, which represents the device’s motion and position relative to the Earth. If required, we can then remap the sensor data, based on the device’s current orientation.

Firstly, the device coordinate system is a standard 3-axis X, Y, Z coordinate system, where each point on each of the three axes is represented by a 3D vector. This means we need to create an array of 9 float values:

       float[] rotationMatrix = new float[9];

We can then pass this array to the getRotationMatrix() method:

       SensorManager.getRotationMatrixFromVector(rotationMatrix, vectors);
       int worldAxisX = SensorManager.AXIS_X;
       int worldAxisZ = SensorManager.AXIS_Z;

The next step, is using the SensorManager.remapCoordinateSystem() method to remap the sensor data, based on the device’s current orientation.

The SensorManager.remapCoordinateSystem() method takes the following arguments:

  • The original rotation matrix.
  • The axes that you want to remap.
  • The array that you’re populating with this new data.

Here’s the code I’ll be using in my app:

  float[] adjustedRotationMatrix = new float[9];
       SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisX, worldAxisZ, adjustedRotationMatrix);

Finally, we’ll call SensorManager.getOrientation and tell it to use the adjustedRotationMatrix:

       SensorManager.getOrientation(adjustedRotationMatrix, orientation);

3. Update the placeholder strings

Since we have two sets of data (pitch and roll), we need to retrieve two separate placeholder strings, populate them with the correct values, and then update the corresponding TextView:

    pitchTextView.setText(getResources().getString(
               R.string.pitch_sensor,pitch));
       rollTextView.setText(getResources().getString(
              R.string.roll_sensor,roll));

Displaying multiple sensor data: Completed code

After performing the above steps, your MainActivity should look something like this:

import android.app.Activity;
import android.os.Bundle;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.widget.TextView;

public class MainActivity extends Activity implements SensorEventListener {

   private SensorManager motionSensorManager;
   private Sensor motionSensor;
   private TextView pitchTextView;
   private TextView rollTextView;

   private static final int SENSOR_DELAY = 500 * 1000;
   private static final int FROM_RADS_TO_DEGS = -57;

   @Override
       protected void onCreate(Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.activity_main);
       pitchTextView = (TextView) findViewById(R.id.pitchTextView);
       rollTextView = (TextView) findViewById(R.id.rollTextView);

       try {
           motionSensorManager = (SensorManager) getSystemService(Activity.SENSOR_SERVICE);
           motionSensor = motionSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
           motionSensorManager.registerListener(this, motionSensor, SENSOR_DELAY);
       } catch (Exception e) {
           pitchTextView.setText(R.string.no_sensor);
           rollTextView.setText(R.string.no_sensor);

       }
   }

   @Override
   public void onAccuracyChanged(Sensor sensor, int accuracy) {

//To do//

   }

   @Override
   public void onSensorChanged(SensorEvent event) {
       if (event.sensor == motionSensor) {
           update(event.values);
       }
   }

   private void update(float[] vectors) {

//Compute the rotation matrix//

       float[] rotationMatrix = new float[9];
       SensorManager.getRotationMatrixFromVector(rotationMatrix, vectors);
       int worldAxisX = SensorManager.AXIS_X;
       int worldAxisZ = SensorManager.AXIS_Z;

//Remap the matrix based on the Activity’s current orientation//

       float[] adjustedRotationMatrix = new float[9];
       SensorManager.remapCoordinateSystem(rotationMatrix, worldAxisX, worldAxisZ, adjustedRotationMatrix);

//Compute the device's orientation//

       float[] orientation = new float[3];

//Supply the array of float values to the getOrientation() method//

      SensorManager.getOrientation(adjustedRotationMatrix, orientation);
      float pitch = orientation[1] * FROM_RADS_TO_DEGS;
      float roll = orientation[2] * FROM_RADS_TO_DEGS;

//Update the TextViews with the pitch and roll values//

       pitchTextView.setText(getResources().getString(
               R.string.pitch_sensor,pitch));
       rollTextView.setText(getResources().getString(
               R.string.roll_sensor,roll));

   }

}

You can download the completed project from GitHub.

Testing our final Android sensor application

To test this rotation vector Android sensor app on a physical Android smartphone or tablet:

  • Install the application on your device.
  • Place your smartphone or tablet on a flat surface. Note that motion sensors are extremely sensitive, so it’s not unusual for a seemingly-motionless device to report fluctuations in pitch and roll values.
  • To test the pitch, lift the bottom of your device so that it’s tilting away from you. The pitch value should change dramatically.
  • To test the roll, try lifting the left-hand side of your device, so it’s tilting to the left – keep an eye on that roll value!

If you’re testing your project on an emulator:

  • Install the application on your AVD.
  • Select “More,” which launches the “Extended Controls” window.
  • In the left-hand menu, select “Virtual sensors.”
  • Make sure the “Accelerometer” tab is selected. This tab contains controls that can simulate changes in the device’s position and orientation.
  • Try experimenting with the various sliders (Rotate: Z-Rot, X-Rot, Y-Rot; and Move: X, Y, and Z) and the various “Device Rotation” buttons, to see how they affect your application’s “Roll Sensor” and “Pitch Sensor” values.

Android sensors - how they work

Wrapping up

In this article, we saw how to retrieve data from the three main categories of Android sensors: environmental, position and motion, and how to monitor this data in real time.

Have you seen any Android apps that use sensors in interesting or unique ways? Let us know in the comments below!

via Android Authority

January 29, 2019 at 01:23PM