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]));
}