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