Sunday, January 26, 2014

Introduction to Using Android's Built-In Sensors

    One of the key characteristics of mobile devices that separates them from older platforms is that they carry a number of embedded sensors, which allow them to take readings from their environment. I have written a sample program that displays available sensors on an Android device, which then shows information and output from a selected sensor. All source code can be found here.

    The initial screen is a simple ListFragment that populates an adapter with a list of sensors on the device:

SensorListFragment.java
@Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        mAdapter = new SensorListAdapter( getActivity() );
        setListAdapter(mAdapter);

        mSensorManager = (SensorManager) getActivity()
                .getSystemService( Context.SENSOR_SERVICE );

        for( Sensor sensor : mSensorManager.getSensorList( Sensor.TYPE_ALL ) )
            mAdapter.add( sensor );
    }

    This adapter displays a simple_list_item_1 list row that is populated with the name of each sensor, so that the result looks like this on a Nexus 4:



    Using the ListFragment's built-in OnListItemClicked function, we can retrieve the sensor that has been selected by the user and pass it to another fragment that replaces the one currently attached to our activity.

SensorListFragment.java
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        super.onListItemClick(l, v, position, id);

        Sensor sensor = (Sensor) getListAdapter().getItem( position );
        SensorDetailFragment mFragment = SensorDetailFragment.newInstance( sensor );
        FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.container, mFragment)
                .addToBackStack(null)
                .commit();
    }

    The sensor is passed to the SensorDetailFragment and the type is stored as an argument that can be retrieved when the fragment is attached and created.

SensorDetailFragment.java
public static SensorDetailFragment newInstance( Sensor sensor ) {
        SensorDetailFragment mFragment = new SensorDetailFragment();
        Bundle args = new Bundle();
        args.putInt( EXTRA_SENSOR_TYPE , sensor.getType() );
        mFragment.setArguments(args);
        return mFragment;
    }

    When the fragment is attached and onCreate() is called, the SensorManager system service is retrieved and stored, then the sensor is retrieved from the manager by its type.

SensorDetailFragment.java
        mSensorManager = (SensorManager) getActivity().getSystemService( Context.SENSOR_SERVICE );
        mSensor = mSensorManager.getDefaultSensor( type );

    After the TextViews, which label and display information, are initialized, the static information from the sensor is displayed using the sensor's various functions found here. The dynamic information, such as the readings from the sensor, are retrieved using the SensorEventListener. This interface consists of two methods that respond to events: onAccuracyChanged( Sensor sensor, int accuracy ) and onSensorChanged( SensorEvent event ).


    onAccuracyChanged passes the sensor that triggered the event, as well as an integer that matches with one of four values representing high, medium, low and unreliable accuracy of the given sensor.

    onSensorChanged passes a SensorEvent item that consists of various attributes such as

  • accuracy
  • timestamp - useful for throwing out data that occurs more rapidly than needed by the application, but not already associated with a sampling speed
  • sensor - the sensor that triggered the event
  • values - an array of the actual readings from the sensor. This can have indices 0 - 3 populated, depending on the type of sensor giving the reading. Some, such as the gravity sensor, give readings for values[0] - values[2] for the X, Y and Z axis of the device, while others, such as the barometer, only give readings in values[0].
    As with any listener interface, it is important to remember to register and unregister your listeners in a practical place, so as to not waste the device battery while the sensors are not being checked and their data used. In this sensor program, the listener is registered in onStart

SensorDetailFragment.java
mSensorManager.registerListener( SensorDetailFragment.this, mSensor, SensorManager.SENSOR_DELAY_UI );

and unregistered when the fragment is hidden.

SensorDetailFragment.java
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
        if( hidden )
            mSensorManager.unregisterListener( this );
    }

    While there is a lot more to sensor-use in the Android platform, especially in how the data can be interpreted and used to take your apps to the next level, I hope I have given a decent introduction to retrieving data from a list of sensors, and that the source code on GitHub can help someone out there looking to implement sensors in their own projects.