Android Sensors
Sensors measure a particular kind of physical quantity, such as force acting on device, light falling on a surface, or the temperature in a room.
The majority of the sensors are Micro Electro Mechanical Sensors (MEMS), which are made on a tiny scale (in micrometers), usually on a silicon chip, with mechanical and electrical elements integrated together. The basic working principle behind MEMS is to measure the change in electric signal originating due to mechanical motion. This change in electric signals is converted to digital values by electric circuits. The accelerometer and gyroscope are the main examples of MEMS
Types of sensor values
Sensor values can be broadly divided into the following three categories:
- Raw: These values are directly given by the sensor. The operating system simply passes these values to the apps without adding any correction logic. Accelerometers, proximity sensors, light sensors, and barometers are sensors that give raw values.
- Calibrated: These values are computed by the operating system by adding extra correction algorithms, such as drift compensation and removing bias and noise over the raw values given by sensors. Step detector, step counter, and significant motion are sensors that give calibrated values by using an accelerometer as their base sensor. The magnetometer and gyroscope are special kinds of sensor that give both raw and calibrated values.
- Fused: These values are derived from a combination of two or more sensors. Generally, these values are calculated by leveraging the strength of one sensor to accommodate the weaknesses of other sensors. Gravity and linear acceleration give fused values by using the accelerometer and gyroscope.
Types of sensor
Sensor
|
Value
|
Underlying Sensors
|
Description
|
Common Usage
|
Accelerometer
|
Raw
|
Accelerometer
|
This measures the acceleration force along the x, y, and z axes (including gravity). Unit: m/s2
|
It can be used to detect motion such as shakes, swings, tilt, and physical forces applied on the phone.
|
Gravity
|
Fused
|
Accelerometer, Gyroscope
|
This measures the force of gravity along the x, y, and zaxes. Unit: m/s2
|
It can be used to detect when the phone is in free fall.
|
Linear Acceleration
|
Fused
|
Accelerometer, Gyroscope
|
It measures the acceleration force along the x, y, and z axes (excluding gravity). Unit: m/s2
|
It can be used to detect motion such as shakes, swings, tilt, and physical forces applied on phone.
|
Gyroscope
|
Raw, Calibrated
|
Gyroscope
|
This measures the rate of rotation of the device along the x, y, and zaxes. Unit: rad/s
|
It can be used to detect rotation motions such as spin, turn, and any angular movement of the phone.
|
Step Detector
|
Calibrated
|
Accelerometer
|
This detects walking steps.
|
It can be used to detect when a user starts walking.
|
Step Counter
|
Calibrated
|
Accelerometer
|
It measures the number of steps taken by the user since the last reboot while the sensor was activated
|
It keeps track of the steps taken by the user per day.
|
Significant Motion
|
Calibrated
|
Accelerometer
|
It detects when there is significant motion on the phone because of walking, running, or driving.
|
It detects a significant motion event.
|
Rotation Vector
|
Fused
|
Accelerometer, Gyroscope, Magnetometer
|
This measures the rotation vector component along the xaxis (x * sin(θ/2)), y axis (y * sin(θ/2)), and z axis (z * sin(θ/2)). Scalar component of the rotation vector ((cos(θ/2)). Unitless.
|
It can be used in 3D games based on phone direction.
|
Android Sensor Stack
Components of the sensor framework
Android has provided methods, classes, and interfaces for accessing sensors and their data that is available on an Android device. These sets of methods, classes, and interfaces are collectively referred to as the sensor framework and are a part of the
android.hardware
package. It consists of four major components: SensorManager
, Sensor
, SensorEvent
, and SensorEventListener
. The entry point to the framework is the SensorManager
class, which allows an app to request sensor information and register to receive sensor data. When registered, sensor data values are sent to a SensorEventListener
interface in the form of a SensorEvent
class that contains information produced from a given sensor.
SensorManager
SensorManager
is the class that makes it possible for your app to get access to the sensors. It creates the instance of the system sensor service, which provides various APIs to access sensor information on the device. It exposes the methods that list the available and default sensors on the device. This class also provides several sensor constants that are used to report sensor accuracy, sampling period, and calibrate sensors. One of the important tasks of this class is to register and unregister sensor event listeners for accessing a particular sensor.SensorEventListener
SensorEventListener is the interface that provides two callbacks to receive the sensor notification (sensor event). OnSensorChanged() is the first method of the interface, which is called whenever there is any change in the sensor values. The change in sensor value is communicated through the SensorEvent object, passed as a parameter to this method. OnAccuracyChanged() is the second method, which is called whenever there is a change in the accuracy of sensor values. The sensor object and newly reported accuracy in integers are sent as parameters to this method. There are four accuracy integer constants supported by SensorManager. They are as follows:
- SENSOR_STATUS_ACCURACY_HIGH
- SENSOR_STATUS_ACCURACY_MEDIUM
- SENSOR_STATUS_ACCURACY_LOW
- SENSOR_STATUS_ACCURACY_UNRELIABLE
Sensor
Sensor is the class that is used to create an instance of a specific sensor. This class provides various methods that let you determine a sensor's capabilities:
- Maximum Range
- Minimum Delay
- Name
- Power
- Resolution
- Reporting Mode
- Type
- Vendor
- Version
- isWakeUp Sensor
SensorEvent
SensorEvent is a special kind of class that is used by the operating system to report changes in the sensor values to the listeners. This SensorEvent object contains the following four elements:
- values[]: This is a multidimensional array that holds the sensor values
- timestamp: This refers to the time in nanoseconds at which the event happened
- accuracy: This is one of the four accuracy integer constants
- sensor: This is the sensor type that generated this data
Checking the availability of the sensor at runtime
private SensorManager mSensorManager; ... mSensorManager= (SensorManager)getSystemService(Context.SENSOR_SERVICE); if(mSensorManager.getDefaultSensor(Sensor.TYPE_PRESSURE)!=null){ // Success! There's a pressure sensor. }else{ // Failure! No pressure sensor. }
Example
import android.app.Activity;
import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Bundle; public class SensorActivity extends Activity implements SensorEventListener{ private SensorManager mSensorManager; private Sensor mSensor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSensorManager = (SensorManager)this.getSystemService (Context.SENSOR_SERVICE ); if(mSensorManager.getDefaultSensor (Sensor.TYPE_GYROSCOPE)!= null){ mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); } } @Override protected void onResume() { super.onResume(); mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL); } @Override protected void onPause() { super.onPause(); mSensorManager.unregisterListener(this); } @Override protected void onDestroy() { super.onDestroy(); mSensorManager = null; mSensor = null; } @Override public void onSensorChanged(SensorEvent event) { //event.values[] (do something with sensor values) //event.timestamp (do something with timestamp) } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { //Do something with changed accuracy //This method is mandatory to defined }
Listing the available sensors on a device
public class SensorListActivity extends Activity implements OnItemClickListener{ private SensorManager mSensorManager; private ListView mSensorListView; private ListAdapter mListAdapter; private List<Sensor> mSensorsList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mSensorManager = (SensorManager)this.getSystemService (Context.SENSOR_SERVICE); mSensorsList = mSensorManager.getSensorList(Sensor.TYPE_ALL); mSensorListView = (ListView)findViewById(R.id.session_list); mListAdapter = new ListAdapter(); mSensorListView.setAdapter(mListAdapter); mSensorListView.setOnItemClickListener(this); } private class ListAdapter extends BaseAdapter{ private TextView mSensorName; @Override public int getCount() { return mSensorsList.size(); } @Override public Object getItem(int position) { return mSensorsList.get(position).getName(); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null){ convertView = getLayoutInflater().inflate(R.layout.list_rows, parent, false); } mSensorName = (TextView)convertView.findViewById(R.id.sensor_name); mSensorName.setText(mSensorsList.get(position) .getName()); return convertView; } } @Override public void onItemClick(AdapterView<?> parent, View view, int position,long id) { Intent intent = new Intent(getApplicationContext(), SensorCapabilityActivity.class); intent.putExtra(getResources() .getResourceName(R.string.sensor_type), mSensorsList.get(position).getType()); startActivity(intent); }
Wake locks, wakeup sensors, and the FIFO queue
Wake lock can be obtained using the
PowerManager
object, which is provided by the system power service. The newWakeLock()
method of PowerManager
provides the object of wake lock. This newWakeLock()
method accepts the type of wake lock and string tag for identification purposes.PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,"myLock"); mWakeLock.acquire(); //Do some important work in background. mWakeLock.release();
Using the fingerprint sensor
In order to support the fingerprint sensor, the Android platform has introduced a new system service, which is called the Finger Print Service, and it can be accessed using the instance of
FingerprintManager
.
Fingerprint sensor APIs require install time permission in the
AndroidManifest.xml
file (android.permission.USE_FINGERPRINT
) and also runtime permission before using them.public class FingerPrintActivity extends Activity { private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0; private FingerprintManager mFingerprintManager; //Alias for our key in the Android Key Store private static final String KEY_NAME = "my_key"; private KeyStore mKeyStore; private KeyGenerator mKeyGenerator; private Cipher mCipher; private CancellationSignal mCancellationSignal; private Dialog mFingerPrintDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.fingerprint_layout); mFingerprintManager = (FingerprintManager)getSystemService (FINGERPRINT_SERVICE); //As soon as Activity starts, check for the finger print conditions checkFingerPrintConditions() } public void initiateFingerPrintSensor(View v) { //Called from Layout button checkFingerPrintConditions(); } public void checkFingerPrintConditions() { if(mFingerprintManager.isHardwareDetected()) { if(mFingerprintManager.hasEnrolledFingerprints()) { if(ContextCompat.checkSelfPermission(this, Manifest.permission.USE_FINGERPRINT)!= PackageManager.PERMISSION_GRANTED) { //Requesting runtime finger print permission requestPermissions(new String[] {Manifest.permission.USE_FINGERPRINT}, FINGERPRINT_PERMISSION_REQUEST_CODE); } else { //After all 3 conditions are met, then show FingerPrint Dialog showFingerPrintDialog(); } } else { showAlertDialog("Finger Print Not Registered!", "Go to 'Settings -> Security -> Fingerprint' and register at least one fingerprint"); } } else { showAlertDialog("Finger Print Sensor Not Found!", "Finger Print Sensor could not be found on your phone."); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) { //show FingerPrint Dialog, when runtime permission is granted if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) { showFingerPrintDialog(); } } public void showAlertDialog(String title, String message){ new android.app.AlertDialog.Builder(this).setTitle(title) .setMessage(message).setIcon(android.R.drawable.ic_dialog_alert) .setPositiveButton("Cancel", new DialogInterface .OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.dismiss(); }}) .show(); } public void showFingerPrintDialog() { //First Initialize the FingerPrint Settings if(initFingerPrintSettings()) { //Init Custom FingerPrint Dialog from xml mFingerPrintDialog = new Dialog(this); View view = LayoutInflater.from(this).inflate (R.layout.fingerpring_dialog, null, false); mFingerPrintDialog.setContentView(view); Button cancel = (Button) view.findViewById(R.id.cancelbutton); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { mCancellationSignal.cancel(); mFingerPrintDialog.dismiss(); } }); //Stops the cancelling of the fingerprint dialog //by back press or touching accidentally on screen mFingerPrintDialog.setCanceledOnTouchOutside(false); mFingerPrintDialog.setCancelable(false); mFingerPrintDialog.show(); } else { showAlertDialog("Error!", "Error in initiating Finger Print Cipher or Key!"); } } public boolean initFingerPrintSettings() { //CancellationSignal requests authenticate api to stop scanning mCancellationSignal = new CancellationSignal(); if(initKey() && initCipher()) { mFingerprintManager.authenticate(new FingerprintManager.CryptoObject(mCipher), mCancellationSignal, 0, new AuthenticationListener(), null); return true; } else { return false; } } public boolean initKey() { try { mKeyStore = KeyStore.getInstance("AndroidKeyStore"); mKeyStore.load(null); mKeyGenerator = KeyGenerator.getInstance (KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); mKeyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) .build()); mKeyGenerator.generateKey(); return true; } catch (Exception e) { return false; } } public boolean initCipher() { try { mKeyStore.load(null); SecretKey key = (SecretKey) mKeyStore.getKey(KEY_NAME, null); mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7); mCipher.init(Cipher.ENCRYPT_MODE, key); return true; } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | IOException | NoSuchAlgorithmException | InvalidKeyException | NoSuchPaddingException e) { return false; } } class AuthenticationListener extends FingerprintManager.AuthenticationCallback{ @Override public void onAuthenticationError(int errMsgId, CharSequence errString) { Toast.makeText(getApplicationContext(), "Authentication Error!", Toast.LENGTH_LONG).show(); } @Override public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) { } @Override public void onAuthenticationFailed() { Toast.makeText(getApplicationContext(), "Authentication Failed!", Toast.LENGTH_LONG).show(); } @Override public void onAuthenticationSucceeded (FingerprintManager.AuthenticationResult result) { Toast.makeText(getApplicationContext(), "Authentication Success!", Toast.LENGTH_LONG).show(); mFingerPrintDialog.dismiss(); } }
The Step Counter and Detector Sensors – The Pedometer App
public class StepsCounterActivity extends Activity
implements SensorEventListener{ private SensorManager mSensorManager; private Sensor mSensor; private boolean isSensorPresent; private TextView mStepsSinceReboot; @Override protected void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); setContentView(R.layout.stepcounter_layout); mStepsSinceReboot = (TextView)findViewById (R.id.stepssincereboot); mSensorManager = (SensorManager) this.getSystemService(Context.SENSOR_SERVICE); if(mSensorManager.getDefaultSensor (Sensor.TYPE_STEP_COUNTER) != null) { mSensor = mSensorManager.getDefaultSensor (Sensor.TYPE_STEP_COUNTER); isSensorPresent = true; } else { isSensorPresent = false; } } @Override protected void onResume() { super.onResume(); if(isSensorPresent) { mSensorManager.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_NORMAL); } } @Override protected void onPause() { super.onPause(); if(isSensorPresent) { mSensorManager.unregisterListener(this); } } @Override public void onSensorChanged(SensorEvent event) { mStepsSinceReboot.setText("Steps since reboot:" + String.valueOf(event.values[0])); }