In the previous article, We learned about the basic properties of the SensorManager, SensorEventListner and Sensor objects along with their usage. In this article we will look closer at some of the more advanced sensor properties and processing sensor data.

 

Trigger Events

In addition to the previously discussed SensorEventListener, Android 4.3 introduced an additional mechanism for receiving sensor data: the TriggerEventListener. TriggerEventListener is mainly used for a special sensor type called significant motion. The signification motion sensor is a software sensor derived from hardware sensors present on the device. The only allowed value the sensor can return is 1.0. So what does this sensor actually do? It detects when the Android device has a sudden motion or jerk.  Unlike SensorEventListener, the TriggerEventListener is a one shot: it is automatically canceled after the trigger.  At the Java level, one major difference between the two listener types is that TriggerEventListener is a class not an interface, so we need to extend and implement the overridden method onTrigger().  The onTrigger() method receives a TriggerEvent object as a parameter. The TriggerEvent class is nearly the same as the SensorEvent class, except it doesn’t have onAccuracyChanged() method.  This is because the trigger event is only fired a single time, after that it will unregister itself.  Here’s a brief example of using TriggerEventListener:

 

public class TriggerListener extends TriggerEventListener {
    private TextView mText;

    public TriggerListener(TextView textView){
        this.mText = textView;
    }

    @Override
    public void onTrigger(TriggerEvent event) {
        Log.i("TriggerListener", "Name:" + event.sensor.getName());
        Log.i("TriggerListener","Type:" + event.sensor.getType());

        mText.setText("Name: " +
                      event.sensor.getName() +
                      "\nType: " +
                      event.sensor.getType());
    }
}

 

public class TriggerEventDemo extends Activity {
    private SensorManager   mSensorManager;
    private TriggerListener mListener;
    private Sensor          mSensor;
    private TextView        mText;

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

        mSensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
        mListener = new TriggerListener(txtTriggerEvent);
        mSensor = sensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.requestTriggerSensor(mListener, mSensor);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.cancelTriggerSensor(mListener, mSensor);
    }
}

 

 Advanced Sensor Properties

Previously we learned of a couple of methods available from the SensorManager which can be used to retrieve Sensor objects. The Sensor objects can provide us with more advanced information, such as the maximum range of values available from a specific sensor.  Let’s take a look at some of these properties in more detail.

getMaximumRange() returns a float value of the maximum range the sensor can report as per its unit of measure. Binary sensors like proximity sensors report only the near or far measurement. In this case the sensor returns the maximum range value for “far” and a lesser value for “near”.

getResolution() returns a float value of the resolution unit of sensor. The resolution represent the minimum detectable difference between two signals.

getReportingMode() returns an integer value indicating the report mode of the sensor, in which there are several options. Several types of sensors, such as the accelerometer, send continuous signals while other sensors, such as the proximity sensor, send only one signal. To identify the sensor reporting mode at runtime, Android 5.0 (API 21) introduced this method. The goal is to allow developers to better process the incoming data. The Sensor class defines the available modes:

  • REPORTING_MODE_CONTINUOUS: Sensor reports events at a constant rate configured via the registerListener() method.
  • REPORTING_MODE_ON_CHANGE: Sensor reports an event only once. To detect this event, the app must register using requestTriggerSensor().
  • REPORTING_MODE_ONE_SHOT: Sensor reports an event only when the value changes. The app can use registerListener() to fetch data from this kind of sensor.
  • REPORTING_MODE_SPECIAL_TRIGGER: The sensor itself has a defined update rate. This sensor ignores the rate of event delivery provided in the call to registerListener().

getMinDelay() returns an integer value representing the minimum delay (in milliseconds) between two events.

getPower() returns a float value indicating the power (in mA) used by this sensor while it is active. This can be used to calculate power consumption of the sensor.

isWakeUpSensor() indicates whether this sensor is one which can wake up the device.  It was introduced in Android Lollipop (API 21). The difference between a wakeup and non-wakeup sensor is defined as follows:

  • Non-Wakeup Sensor: When the mobile device is in suspend or sleep mode and this type of sensor receives an event, the sensor stores its data into memory but does not deliver it to the device. Sensors have a maximum limit in which to store sensor data, which can be retrieved using  the sensor object’s getFifoMaxEventCount() method.
  • Wakeup Sensor: When the mobile device is in suspend or sleep mode and sensor receives event the device will be awakened and the data delivered.

 

Sensor Event Listener with Handler

As we saw in the previous article, the registered SensorEventListener is called back on the main thread, or UI thread, of the application. For simple use cases or demos, this works well. However, when you want to decouple sensor processing from the main thread, the SensorManager has an overloaded registerListener() method which accepts a Handler as an argument. Using this approach, the sensor data can be delivered to an alternate thread for handling:

public class HandlerDemo extends Activity implements SensorEventListener{
    private SensorManager mSensorManager;
    private Sensor        mSensor;
    private Handler       mHandler;
    private HandlerThread mHandlerThread;
    private TextView      mText;

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

        mHandlerThread = new HandlerThread("SensorThread");
        mHandlerThread.start();
        mHandler = new Handler(mHandlerThread.getLooper());

        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        mSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
    }

    @Override
    protected void onResume() {
        super.onResume();
        mSensorManager.registerListener(this,
                                       mSensor,
                                       SensorManager.SENSOR_DELAY_NORMAL,
                                       mHandler);
    }

    @Override
    protected void onPause() {
        super.onPause();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        final StringBuilder sb = new StringBuilder();
        sb.append("Sensor Name: ");
        sb.append(event.sensor.getName());
        sb.append("\n");
        sb.append("Time: ");
        sb.append(event.timestamp);
        sb.append("\n");
        sb.append("Accuracy: ");
        sb.append(event.accuracy);
        sb.append("\n");

        for(float v : event.values){
            sb.append(v);
            sb.append("\n");
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                   mText.setText(sb.toString());
            }
        });

    }
    ...
}

 

Processing Sensor Data In a Service

Our examples so far have been with UI based applications showing data. What about if an application needs sensor data but does not require a UI while active? A simple example of this is a pedometer. For this, we can leverage background processing using an Android Service.

In this demo we will use logcat to show the data in order to keep it simple. First, we need to create a Service and implement the SensorEventListener interface. Previously, we retrieved the SensorManager object in the Activity‘s onCreate() method. Following the same pattern, we can get the SensorManager object in the onCreate() method of the Service. We will use the onDestory() method of our Service to unregister our listener. The ServiceDemo application uses an Activity with two buttons to start and stop the Service, respectively. When the user clicks the “Start Service” button, the Service will start if it is not already and we will see accelerometer data in logcat. We can then exit the UI for our app and continue to see accelerometer logs. This mechanism along with using a custom Handler in an alternate thread provides us with a flexible way to handle incoming sensor data, even when our app’s UI is not in the foreground for the user. The Service code is shown below. Refer to the full sources to see the Activity and layout files.

public class SensorService extends Service implements SensorEventListener {
    private SensorManager  mSensorManager;
    private Sensor         mSensor;
    ...
    @Override
    public void onCreate() {
        super.onCreate();
        mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
        mSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        sensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return Service.START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        mSensorManager.unregisterListener(this);
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        final StringBuilder sb = new StringBuilder();
        sb.append("Sensor Name: ");
        sb.append(event.sensor.getName());
        sb.append("\n");
        sb.append("Time: ");
        sb.append(event.timestamp);
        sb.append("\n");
        sb.append("Accuracy: ");
        sb.append(event.accuracy);
        sb.append("\n");

        for (float v : event.values) {
            sb.append(v);
            sb.append("\n");
        }

        Log.i(TAG, sb.toString());
    }
    ...
}

 

Recap

In this article we explored some of Android’s advanced sensor handling. We began by looking at Trigger sensors, which are different from traditional sensors defined by the Android framework. One of the key differences is that a Trigger sensor is automatically unregistered after it fires. In order to capture trigger events, we need to extend TriggerEventListener class and implement the onTrigger() method. Next, we examined some of the advanced sensor properties, including some which were introduced in Android 5.0. Advanced sensor properties, such as the power usage, maximum range and reporting mode, are very valuable for apps which make heavy use of sensors. It allows the app to better understand the data arriving from the sensor and the impact using it has on the mobile device’s power consumption. Finally, we took a look at how to use a sensor in an alternate thread via a Handler as well as within a Service. Using one or both of mechanisms an app can leverage sensors without bogging down the UI of the system or even being shown.

The sources used in this article can be found at the following GitHub repository:
https://github.com/hiq-larryschiefer/AdvSensors